Ver Fonte

Initial import

Rashid Khan há 13 anos atrás
pai
commit
0ee868b382

+ 19 - 1
README.md

@@ -1,4 +1,22 @@
 kibana-dashboard
 ================
 
-Kibana Dashboard Preview
+Kibana Dashboard Preview
+
+This is very much a preview, many things will change. While it is functional and
+useful, please view it as a proof-of-concept. A play ground for ideas :-)
+
+Configuration is in config.js, the default dashboard is in dashboards.js. The
+format of both of these is likely to change. Documentation for the panels
+listed in dashboards.js can be found in js/panels.js
+
+There is an example of a sharable dashboard in sharable.json. The file loading
+functionality requires an html5 compliant browser. This has been tested on the 
+latest versions of firefox and chrome. 
+
+This is all html and javascript, use it with any webserver, or there is a simple
+nodejs webserver in the scripts/ directory, it will listen on port 8000. You'll
+likely need to run Kibana Dashboard on an elasticsearch node in any case.
+
+Cheers  
+Rashid

BIN
common/.DS_Store


Diff do ficheiro suprimidas por serem muito extensas
+ 8 - 0
common/css/bootstrap-responsive.min.css


Diff do ficheiro suprimidas por serem muito extensas
+ 8 - 0
common/css/bootstrap.min.css


+ 156 - 0
common/css/datepicker.css

@@ -0,0 +1,156 @@
+/* =========================================================
+ * bootstrap-datepicker.js
+ * original by Stefan Petre
+ * tweaked by gus
+ * ========================================================= */
+
+.bs-sc-datepicker.dropdown-menu {
+max-width: inherit;
+}
+.bs-sc-datepicker {
+top: 0;
+left: 0;
+padding: 4px;
+margin-top: 1px;
+-webkit-border-radius: 4px;
+-moz-border-radius: 4px;
+border-radius: 4px;
+z-index: 1051;
+}
+.bs-sc-datepicker:before {
+content: '';
+display: inline-block;
+border-left: 7px solid transparent;
+border-right: 7px solid transparent;
+border-bottom: 7px solid #ccc;
+border-bottom-color: rgba(0, 0, 0, 0.2);
+position: absolute;
+top: -7px;
+left: 6px;
+}
+.bs-sc-datepicker:after {
+content: '';
+display: inline-block;
+border-left: 6px solid transparent;
+border-right: 6px solid transparent;
+border-bottom: 6px solid #ffffff;
+position: absolute;
+top: -6px;
+left: 7px;
+}
+.bs-sc-datepicker table {
+width: 100%;
+margin: 0;
+}
+.bs-sc-datepicker th {
+border-bottom: 1px solid #efefef;
+}
+.bs-sc-datepicker td, .bs-sc-datepicker th {
+text-align: center;
+width: 20px;
+height: 20px;
+-webkit-border-radius: 4px;
+-moz-border-radius: 4px;
+border-radius: 4px;
+padding: 5px !important;
+}
+.bs-sc-datepicker td.day:hover {
+background: #eeeeee;
+cursor: pointer;
+}
+.bs-sc-datepicker td.old, .bs-sc-datepicker td.new {
+color: #DDDDDD;
+}
+.bs-sc-datepicker td.active, .bs-sc-datepicker td.active:hover {
+background-color: #006dcc;
+background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+background-image: linear-gradient(top, #0088cc, #0044cc);
+background-repeat: repeat-x;
+filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+border-color: #0044cc #0044cc #002a80;
+border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+color: #fff;
+text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.bs-sc-datepicker td.active:hover,
+.bs-sc-datepicker td.active:hover:hover,
+.bs-sc-datepicker td.active:active,
+.bs-sc-datepicker td.active:hover:active,
+.bs-sc-datepicker td.active.active,
+.bs-sc-datepicker td.active:hover.active,
+.bs-sc-datepicker td.active.disabled,
+.bs-sc-datepicker td.active:hover.disabled,
+.bs-sc-datepicker td.active[disabled],
+.bs-sc-datepicker td.active:hover[disabled] {
+background-color: #0044cc;
+}
+.bs-sc-datepicker td.active:active,
+.bs-sc-datepicker td.active:hover:active,
+.bs-sc-datepicker td.active.active,
+.bs-sc-datepicker td.active:hover.active {
+background-color: #003399 \9;
+}
+.bs-sc-datepicker td span {
+display: block;
+width: 47px;
+height: 54px;
+line-height: 54px;
+float: left;
+margin: 2px;
+cursor: pointer;
+-webkit-border-radius: 4px;
+-moz-border-radius: 4px;
+border-radius: 4px;
+}
+.bs-sc-datepicker td span:hover {
+background: #eeeeee;
+}
+.bs-sc-datepicker td span.active {
+background-color: #006dcc;
+background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+background-image: linear-gradient(top, #0088cc, #0044cc);
+background-repeat: repeat-x;
+filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+border-color: #0044cc #0044cc #002a80;
+border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+color: #fff;
+text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.bs-sc-datepicker td span.active:hover,
+.bs-sc-datepicker td span.active:active,
+.bs-sc-datepicker td span.active.active,
+.bs-sc-datepicker td span.active.disabled,
+.bs-sc-datepicker td span.active[disabled] {
+background-color: #0044cc;
+}
+.bs-sc-datepicker td span.active:active, .bs-sc-datepicker td span.active.active {
+background-color: #003399 \9;
+}
+.bs-sc-datepicker td span.old {
+color: #999999;
+}
+.bs-sc-datepicker th.switch {
+width: 145px;
+}
+.bs-sc-datepicker thead tr:first-child th {
+cursor: pointer;
+}
+.bs-sc-datepicker thead tr:first-child th:hover {
+background: #eeeeee;
+}
+.input-append.date .add-on i, .input-prepend.date .add-on i {
+display: block;
+cursor: pointer;
+width: 16px;
+height: 16px;
+}

+ 6 - 0
common/css/elasticjs.css

@@ -0,0 +1,6 @@
+.axis path,
+.axis line {
+    fill: none;
+    stroke: #000;
+    shape-rendering: crispEdges;
+}

+ 11 - 0
common/css/main.css

@@ -0,0 +1,11 @@
+#events {
+  font-size: 12px;
+}
+
+.legend {
+  color: #000;
+}
+
+#upload {
+  display: inline-block;
+}

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
common/css/normalize.min.css


BIN
common/img/glyphicons-halflings-white.png


BIN
common/img/glyphicons-halflings.png


BIN
common/lib/.DS_Store


+ 158 - 0
common/lib/angular.min.js

@@ -0,0 +1,158 @@
+/*
+ AngularJS v1.0.2
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(T,ba,p){'use strict';function m(b,a,c){var d;if(b)if(M(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(I(b)&&wa(b.length))for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function mb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function fc(b,a,c){for(var d=mb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}
+function nb(b){return function(a,c){b(c,a)}}function xa(){for(var b=Z.length,a;b;){b--;a=Z[b].charCodeAt(0);if(a==57)return Z[b]="A",Z.join("");if(a==90)Z[b]="0";else return Z[b]=String.fromCharCode(a+1),Z.join("")}Z.unshift("0");return Z.join("")}function x(b){m(arguments,function(a){a!==b&&m(a,function(a,d){b[d]=a})});return b}function G(b){return parseInt(b,10)}function ya(b,a){return x(new (x(function(){},{prototype:b})),a)}function D(){}function ma(b){return b}function J(b){return function(){return b}}
+function t(b){return typeof b=="undefined"}function u(b){return typeof b!="undefined"}function I(b){return b!=null&&typeof b=="object"}function F(b){return typeof b=="string"}function wa(b){return typeof b=="number"}function na(b){return Ta.apply(b)=="[object Date]"}function K(b){return Ta.apply(b)=="[object Array]"}function M(b){return typeof b=="function"}function oa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Q(b){return F(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}function gc(b){return b&&
+(b.nodeName||b.bind&&b.find)}function Ua(b,a,c){var d=[];m(b,function(b,g,i){d.push(a.call(c,b,g,i))});return d}function hc(b,a){var c=0,d;if(K(b)||F(b))return b.length;else if(I(b))for(d in b)(!a||b.hasOwnProperty(d))&&c++;return c}function Va(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function za(b,a){var c=Va(b,a);c>=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw A("Can't copy Window or Scope");if(a){if(b===
+a)throw A("Can't copy equivalent objects or arrays");if(K(b)){for(;a.length;)a.pop();for(var c=0;c<b.length;c++)a.push(U(b[c]))}else for(c in m(a,function(b,c){delete a[c]}),b)a[c]=U(b[c])}else(a=b)&&(K(b)?a=U(b,[]):na(b)?a=new Date(b.getTime()):I(b)&&(a=U(b,{})));return a}function ic(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&c.substr(0,2)!=="$$"&&(a[c]=b[c]);return a}function ga(b,a){if(b===a)return!0;if(b===null||a===null)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&
+c=="object")if(K(b)){if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ga(b[d],a[d]))return!1;return!0}}else if(na(b))return na(a)&&b.getTime()==a.getTime();else{if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||oa(b)||oa(a))return!1;c={};for(d in b){if(d.charAt(0)!=="$"&&!M(b[d])&&!ga(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c[d]&&d.charAt(0)!=="$"&&!M(a[d]))return!1;return!0}return!1}function Wa(b,a){var c=arguments.length>2?ha.call(arguments,2):[];return M(a)&&!(a instanceof RegExp)?c.length?
+function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function jc(b,a){var c=a;/^\$+/.test(b)?c=p:oa(a)?c="$WINDOW":a&&ba===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function ca(b,a){return JSON.stringify(b,jc,a?"  ":null)}function ob(b){return F(b)?JSON.parse(b):b}function Xa(b){b&&b.length!==0?(b=E(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;
+return b}function pa(b){b=y(b).clone();try{b.html("")}catch(a){}return y("<div>").append(b).html().match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+E(b)})}function Ya(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=u(c[1])?decodeURIComponent(c[1]):!0)});return a}function pb(b){var a=[];m(b,function(b,d){a.push(Za(d,!0)+(b===!0?"":"="+Za(b,!0)))});return a.length?a.join("&"):""}function $a(b){return Za(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,
+"=").replace(/%2B/gi,"+")}function Za(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(a?null:/%20/g,"+")}function kc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(i,function(a){i[a]=!0;c(ba.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+
+a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function qb(b,a){b=y(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=rb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,i){a.$apply(function(){b.data("$injector",i);c(b)(a)})}]);return c}function ab(b,a){a=a||"_";return b.replace(lc,
+function(b,d){return(d?a:"")+b.toLowerCase()})}function qa(b,a,c){if(!b)throw new A("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&K(b)&&(b=b[b.length-1]);qa(M(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function mc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,
+d,e){return function(){b[e||"push"]([c,d,arguments]);return j}}if(!e)throw A("No module: "+d);var b=[],c=[],k=a("$injector","invoke"),j={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){c.push(a);
+return this}};g&&k(g);return j})}})}function sb(b){return b.replace(nc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(oc,"Moz$1")}function bb(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,k,j,l,n;b.length;){i=b.shift();f=0;for(h=i.length;f<h;f++){k=y(i[f]);c?(n=(j=k.data("events"))&&j.$destroy)&&m(n,function(a){a.handler()}):c=!c;j=0;for(e=(l=k.children()).length,k=e;j<k;j++)b.push(ia(l[j]))}}return d.apply(this,arguments)}var d=ia.fn[b],d=d.$original||d;c.$original=d;ia.fn[b]=c}function P(b){if(b instanceof
+P)return b;if(!(this instanceof P)){if(F(b)&&b.charAt(0)!="<")throw A("selectors not implemented");return new P(b)}if(F(b)){var a=ba.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);cb(this,a.childNodes);this.remove()}else cb(this,b)}function db(b){return b.cloneNode(!0)}function sa(b){tb(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)sa(b[a])}function ub(b,a,c){var d=da(b,"events");da(b,"handle")&&(t(a)?m(d,function(a,c){eb(b,c,a);delete d[c]}):t(c)?(eb(b,a,d[a]),
+delete d[a]):za(d[a],c))}function tb(b){var a=b[Aa],c=Ba[a];c&&(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),ub(b)),delete Ba[a],b[Aa]=p)}function da(b,a,c){var d=b[Aa],d=Ba[d||-1];if(u(c))d||(b[Aa]=d=++pc,d=Ba[d]={}),d[a]=c;else return d&&d[a]}function vb(b,a,c){var d=da(b,"data"),e=u(c),g=!e&&u(a),i=g&&!I(a);!d&&!i&&da(b,"data",d={});if(e)d[a]=c;else if(g)if(i)return d&&d[a];else x(d,a);else return d}function Ca(b,a){return(" "+b.className+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" ")>
+-1}function wb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})}function xb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function cb(b,a){if(a)for(var a=!a.nodeName&&u(a.length)&&!oa(a)?a:[a],c=0;c<a.length;c++)b.push(a[c])}function yb(b,a){return Da(b,"$"+(a||"ngController")+"Controller")}function Da(b,a,c){b=y(b);for(b[0].nodeType==9&&(b=b.find("html"));b.length;){if(c=b.data(a))return c;
+b=b.parent()}}function zb(b,a){var c=Ea[a.toLowerCase()];return c&&Ab[b.nodeName]&&c}function qc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefault=function(){c.returnValue=!1};if(!c.stopPropagation)c.stopPropagation=function(){c.cancelBubble=!0};if(!c.target)c.target=c.srcElement||ba;if(t(c.defaultPrevented)){var g=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented};m(a[e||c.type],
+function(a){a.call(b,c)});$<=8?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function ja(b){var a=typeof b,c;if(a=="object"&&b!==null)if(typeof(c=b.$$hashKey)=="function")c=b.$$hashKey();else{if(c===p)c=b.$$hashKey=xa()}else c=b;return a+":"+c}function Fa(b){m(b,this.put,this)}function fb(){}function Bb(b){var a,c;if(typeof b=="function"){if(!(a=b.$inject))a=[],c=b.toString().replace(rc,
+""),c=c.match(sc),m(c[1].split(tc),function(b){b.replace(uc,function(b,c,d){a.push(d)})}),b.$inject=a}else K(b)?(c=b.length-1,ra(b[c],"fn"),a=b.slice(0,c)):ra(b,"fn",!0);return a}function rb(b){function a(a){return function(b,c){if(I(b))m(b,nb(a));else return a(b,c)}}function c(a,b){M(b)&&(b=l.instantiate(b));if(!b.$get)throw A("Provider "+a+" must define $get factory method.");return j[a+f]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[];m(a,function(a){if(!k.get(a))if(k.put(a,!0),
+F(a)){var c=ta(a);b=b.concat(e(c.requires)).concat(c._runBlocks);try{for(var d=c._invokeQueue,c=0,f=d.length;c<f;c++){var g=d[c],h=g[0]=="$injector"?l:l.get(g[0]);h[g[1]].apply(h,g[2])}}catch(i){throw i.message&&(i.message+=" from "+a),i;}}else if(M(a))try{b.push(l.invoke(a))}catch(o){throw o.message&&(o.message+=" from "+a),o;}else if(K(a))try{b.push(l.invoke(a))}catch(n){throw n.message&&(n.message+=" from "+String(a[a.length-1])),n;}else ra(a,"module")});return b}function g(a,b){function c(d){if(typeof d!==
+"string")throw A("Service name expected");if(a.hasOwnProperty(d)){if(a[d]===i)throw A("Circular dependency: "+h.join(" <- "));return a[d]}else try{return h.unshift(d),a[d]=i,a[d]=b(d)}finally{h.shift()}}function d(a,b,e){var f=[],g=Bb(a),k,i,o;i=0;for(k=g.length;i<k;i++)o=g[i],f.push(e&&e.hasOwnProperty(o)?e[o]:c(o,h));a.$inject||(a=a[k]);switch(b?-1:f.length){case 0:return a();case 1:return a(f[0]);case 2:return a(f[0],f[1]);case 3:return a(f[0],f[1],f[2]);case 4:return a(f[0],f[1],f[2],f[3]);case 5:return a(f[0],
+f[1],f[2],f[3],f[4]);case 6:return a(f[0],f[1],f[2],f[3],f[4],f[5]);case 7:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6]);case 8:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7]);case 9:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]);case 10:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9]);default:return a.apply(b,f)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(K(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return I(e)?e:c},get:c,annotate:Bb}}var i=
+{},f="Provider",h=[],k=new Fa,j={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,J(b))}),constant:a(function(a,b){j[a]=b;n[a]=b}),decorator:function(a,b){var c=l.get(a+f),d=c.$get;c.$get=function(){var a=r.invoke(d,c);return r.invoke(b,null,{$delegate:a})}}}},l=g(j,function(){throw A("Unknown provider: "+h.join(" <- "));}),n={},r=n.$injector=g(n,function(a){a=l.get(a+f);return r.invoke(a.$get,
+a)});m(e(b),function(a){r.invoke(a||D)});return r}function vc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;m(a,function(a){!b&&E(a.nodeName)==="a"&&(b=a)});return b}function g(){var b=c.hash(),d;b?(d=i.getElementById(b))?d.scrollIntoView():(d=e(i.getElementsByName(b)))?d.scrollIntoView():b==="top"&&a.scrollTo(0,0):a.scrollTo(0,0)}var i=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(g)});
+return g}]}function wc(b,a,c,d){function e(a){try{a.apply(null,ha.call(arguments,1))}finally{if(o--,o===0)for(;w.length;)try{w.pop()()}catch(b){c.error(b)}}}function g(a,b){(function ea(){m(q,function(a){a()});v=b(ea,a)})()}function i(){Y!=f.url()&&(Y=f.url(),m(z,function(a){a(f.url())}))}var f=this,h=a[0],k=b.location,j=b.history,l=b.setTimeout,n=b.clearTimeout,r={};f.isMock=!1;var o=0,w=[];f.$$completeOutstandingRequest=e;f.$$incOutstandingRequestCount=function(){o++};f.notifyWhenNoOutstandingRequests=
+function(a){m(q,function(a){a()});o===0?a():w.push(a)};var q=[],v;f.addPollFn=function(a){t(v)&&g(100,l);q.push(a);return a};var Y=k.href,B=a.find("base");f.url=function(a,b){if(a){if(Y!=a)return Y=a,d.history?b?j.replaceState(null,"",a):(j.pushState(null,"",a),B.attr("href",B.attr("href"))):b?k.replace(a):k.href=a,f}else return k.href.replace(/%27/g,"'")};var z=[],L=!1;f.onUrlChange=function(a){L||(d.history&&y(b).bind("popstate",i),d.hashchange?y(b).bind("hashchange",i):f.addPollFn(i),L=!0);z.push(a);
+return a};f.baseHref=function(){var a=B.attr("href");return a?a.replace(/^https?\:\/\/[^\/]*/,""):a};var V={},s="",N=f.baseHref();f.cookies=function(a,b){var d,e,f,g;if(a)if(b===p)h.cookie=escape(a)+"=;path="+N+";expires=Thu, 01 Jan 1970 00:00:00 GMT";else{if(F(b))d=(h.cookie=escape(a)+"="+escape(b)+";path="+N).length+1,d>4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"),V.length>20&&c.warn("Cookie '"+a+"' possibly not set or overflowed because too many cookies were already set ("+
+V.length+" > 20 )")}else{if(h.cookie!==s){s=h.cookie;d=s.split("; ");V={};for(f=0;f<d.length;f++)e=d[f],g=e.indexOf("="),g>0&&(V[unescape(e.substring(0,g))]=unescape(e.substring(g+1)))}return V}};f.defer=function(a,b){var c;o++;c=l(function(){delete r[c];e(a)},b||0);r[c]=!0;return c};f.defer.cancel=function(a){return r[a]?(delete r[a],n(a),e(D),!0):!1}}function xc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new wc(b,d,a,c)}]}function yc(){this.$get=function(){function b(b,
+d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw A("cacheId "+b+" taken");var i=0,f=x({},d,{id:b}),h={},k=d&&d.capacity||Number.MAX_VALUE,j={},l=null,n=null;return a[b]={put:function(a,b){var c=j[a]||(j[a]={key:a});e(c);t(b)||(a in h||i++,h[a]=b,i>k&&this.remove(n.key))},get:function(a){var b=j[a];if(b)return e(b),h[a]},remove:function(a){var b=j[a];if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete j[a];
+delete h[a];i--},removeAll:function(){h={};i=0;j={};l=n=null},destroy:function(){j=f=h=null;delete a[b]},info:function(){return x({},f,{size:i})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function zc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Cb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ";
+this.directive=function f(d,e){F(d)?(qa(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d],function(a){try{var f=b.invoke(a);if(M(f))f={compile:J(f)};else if(!f.compile&&f.link)f.compile=J(f.link);f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(g){c(g)}});return e}])),a[d].push(e)):m(d,nb(f));return this};this.$get=["$injector","$interpolate","$exceptionHandler",
+"$http","$templateCache","$parse","$controller","$rootScope",function(b,h,k,j,l,n,r,o){function w(a,b,c){a instanceof y||(a=y(a));m(a,function(b,c){b.nodeType==3&&(a[c]=y(b).wrap("<span></span>").parent()[0])});var d=v(a,b,a,c);return function(b,c){qa(b,"scope");var e=c?ua.clone.call(a):a;e.data("$scope",b);q(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function q(a,b){try{a.addClass(b)}catch(c){}}function v(a,b,c,d){function e(a,c,d,g){for(var k,h,n,j,o,l=0,r=0,q=f.length;l<q;r++)n=c[r],k=f[l++],
+h=f[l++],k?(k.scope?(j=a.$new(I(k.scope)),y(n).data("$scope",j)):j=a,(o=k.transclude)||!g&&b?k(h,j,n,d,function(b){return function(c){var d=a.$new();return b(d,c).bind("$destroy",Wa(d,d.$destroy))}}(o||b)):k(h,j,n,p,g)):h&&h(a,n.childNodes,p,g)}for(var f=[],g,k,h,n=0;n<a.length;n++)k=new ea,g=Y(a[n],[],k,d),k=(g=g.length?B(g,a[n],k,b,c):null)&&g.terminal?null:v(a[n].childNodes,g?g.transclude:b),f.push(g),f.push(k),h=h||g||k;return h?e:null}function Y(a,b,c,f){var g=c.$attr,k;switch(a.nodeType){case 1:z(b,
+fa(Db(a).toLowerCase()),"E",f);var h,n,j;k=a.attributes;for(var o=0,l=k&&k.length;o<l;o++)if(h=k[o],h.specified)n=h.name,j=fa(n.toLowerCase()),g[j]=n,c[j]=h=Q($&&n=="href"?decodeURIComponent(a.getAttribute(n,2)):h.value),zb(a,j)&&(c[j]=!0),W(a,b,h,j),z(b,j,"A",f);a=a.className;if(F(a))for(;k=e.exec(a);)j=fa(k[2]),z(b,j,"C",f)&&(c[j]=Q(k[3])),a=a.substr(k.index+k[0].length);break;case 3:H(b,a.nodeValue);break;case 8:try{if(k=d.exec(a.nodeValue))j=fa(k[1]),z(b,j,"M",f)&&(c[j]=Q(k[2]))}catch(r){}}b.sort(s);
+return b}function B(a,b,c,d,e){function f(a,b){if(a)a.require=C.require,l.push(a);if(b)b.require=C.require,aa.push(b)}function h(a,b){var c,d="data",e=!1;if(F(a)){for(;(c=a.charAt(0))=="^"||c=="?";)a=a.substr(1),c=="^"&&(d="inheritedData"),e=e||c=="?";c=b[d]("$"+a+"Controller");if(!c&&!e)throw A("No controller: "+a);}else K(a)&&(c=[],m(a,function(a){c.push(h(a,b))}));return c}function j(a,d,e,f,g){var o,q,w,L,Ha;o=b===e?c:ic(c,new ea(y(e),c.$attr));q=o.$$element;if(v&&I(v.scope)){var Y=/^\s*([@=&])\s*(\w*)\s*$/,
+s=d.$parent||d;m(v.scope,function(a,b){var c=a.match(Y)||[],e=c[2]||b,f,g,k;switch(c[1]){case "@":o.$observe(e,function(a){d[b]=a});o.$$observers[e].$$scope=s;break;case "=":g=n(o[e]);k=g.assign||function(){f=d[b]=g(s);throw A(Eb+o[e]+" (directive: "+v.name+")");};f=d[b]=g(s);d.$watch(function(){var a=g(s);a!==d[b]&&(a!==f?f=d[b]=a:k(s,f=d[b]));return a});break;case "&":g=n(o[e]);d[b]=function(a){return g(s,a)};break;default:throw A("Invalid isolate scope definition for directive "+v.name+": "+a);
+}})}u&&m(u,function(a){var b={$scope:d,$element:q,$attrs:o,$transclude:g};Ha=a.controller;Ha=="@"&&(Ha=o[a.name]);q.data("$"+a.name+"Controller",r(Ha,b))});f=0;for(w=l.length;f<w;f++)try{L=l[f],L(d,q,o,L.require&&h(L.require,q))}catch(Ia){k(Ia,pa(q))}a&&a(d,e.childNodes,p,g);f=0;for(w=aa.length;f<w;f++)try{L=aa[f],L(d,q,o,L.require&&h(L.require,q))}catch(B){k(B,pa(q))}}for(var o=-Number.MAX_VALUE,l=[],aa=[],v=null,B=null,z=null,s=c.$$element=y(b),C,H,W,D,t=d,u,x,X,E=0,G=a.length;E<G;E++){C=a[E];W=
+p;if(o>C.priority)break;if(X=C.scope)N("isolated scope",B,C,s),I(X)&&(q(s,"ng-isolate-scope"),B=C),q(s,"ng-scope"),v=v||C;H=C.name;if(X=C.controller)u=u||{},N("'"+H+"' controller",u[H],C,s),u[H]=C;if(X=C.transclude)N("transclusion",D,C,s),D=C,o=C.priority,X=="element"?(W=y(b),s=c.$$element=y("<\!-- "+H+": "+c[H]+" --\>"),b=s[0],Ga(e,y(W[0]),b),t=w(W,d,o)):(W=y(db(b)).contents(),s.html(""),t=w(W,d));if(X=C.template)if(N("template",z,C,s),z=C,X=Ia(X),C.replace){W=y("<div>"+Q(X)+"</div>").contents();
+b=W[0];if(W.length!=1||b.nodeType!==1)throw new A(g+X);Ga(e,s,b);H={$attr:{}};a=a.concat(Y(b,a.splice(E+1,a.length-(E+1)),H));L(c,H);G=a.length}else s.html(X);if(C.templateUrl)N("template",z,C,s),z=C,j=V(a.splice(E,a.length-E),j,s,c,e,C.replace,t),G=a.length;else if(C.compile)try{x=C.compile(s,c,t),M(x)?f(null,x):x&&f(x.pre,x.post)}catch(J){k(J,pa(s))}if(C.terminal)j.terminal=!0,o=Math.max(o,C.priority)}j.scope=v&&v.scope;j.transclude=D&&t;return j}function z(d,e,g,h){var j=!1;if(a.hasOwnProperty(e))for(var n,
+e=b.get(e+c),o=0,l=e.length;o<l;o++)try{if(n=e[o],(h===p||h>n.priority)&&n.restrict.indexOf(g)!=-1)d.push(n),j=!0}catch(r){k(r)}return j}function L(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){f=="class"?(q(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):f=="style"?e.attr("style",e.attr("style")+";"+b):f.charAt(0)!="$"&&!a.hasOwnProperty(f)&&(a[f]=b,d[f]=c[f])})}function V(a,b,c,d,e,
+f,k){var h=[],n,o,r=c[0],q=a.shift(),w=x({},q,{controller:null,templateUrl:null,transclude:null});c.html("");j.get(q.templateUrl,{cache:l}).success(function(j){var l,q,j=Ia(j);if(f){q=y("<div>"+Q(j)+"</div>").contents();l=q[0];if(q.length!=1||l.nodeType!==1)throw new A(g+j);j={$attr:{}};Ga(e,c,l);Y(l,a,j);L(d,j)}else l=r,c.html(j);a.unshift(w);n=B(a,c,d,k);for(o=v(c.contents(),k);h.length;){var aa=h.pop(),j=h.pop();q=h.pop();var s=h.pop(),m=l;q!==r&&(m=db(l),Ga(j,y(q),m));n(function(){b(o,s,m,e,aa)},
+s,m,e,aa)}h=null}).error(function(a,b,c,d){throw A("Failed to load template: "+d.url);});return function(a,c,d,e,f){h?(h.push(c),h.push(d),h.push(e),h.push(f)):n(function(){b(o,c,d,e,f)},c,d,e,f)}}function s(a,b){return b.priority-a.priority}function N(a,b,c,d){if(b)throw A("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function H(a,b){var c=h(b,!0);c&&a.push({priority:0,compile:J(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);q(d.data("$binding",e),
+"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function W(a,b,c,d){var e=h(c,!0);e&&b.push({priority:100,compile:J(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=h(c[d],!0));c[d]=p;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function Ga(a,b,c){var d=b[0],e=d.parentNode,f,g;if(a){f=0;for(g=a.length;f<g;f++)if(a[f]==d){a[f]=c;break}}e&&e.replaceChild(c,d);c[y.expando]=d[y.expando];b[0]=c}var ea=function(a,
+b){this.$$element=a;this.$attr=b||{}};ea.prototype={$normalize:fa,$set:function(a,b,c,d){var e=zb(this.$$element[0],a),f=this.$$observers;e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=ab(a,"-"));c!==!1&&(b===null||b===p?this.$$element.removeAttr(d):this.$$element.attr(d,b));f&&m(f[a],function(a){try{a(b)}catch(c){k(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);o.$evalAsync(function(){e.$$inter||
+b(c[a])});return b}};var D=h.startSymbol(),aa=h.endSymbol(),Ia=D=="{{"||aa=="}}"?ma:function(a){return a.replace(/\{\{/g,D).replace(/}}/g,aa)};return w}]}function fa(b){return sb(b.replace(Ac,""))}function Bc(){var b={};this.register=function(a,c){I(a)?x(b,a):b[a]=c};this.$get=["$injector","$window",function(a,c){return function(d,e){if(F(d)){var g=d,d=b.hasOwnProperty(g)?b[g]:gb(e.$scope,g,!0)||gb(c,g,!0);ra(d,g,!0)}return a.instantiate(d,e)}}]}function Cc(){this.$get=["$window",function(b){return y(b.document)}]}
+function Dc(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Ec(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse",function(c){function d(d,f){for(var h,k,j=0,l=[],n=d.length,r=!1,o=[];j<n;)(h=d.indexOf(b,j))!=-1&&(k=d.indexOf(a,h+e))!=-1?(j!=h&&l.push(d.substring(j,h)),l.push(j=c(r=d.substring(h+e,k))),j.exp=r,j=k+g,r=!0):(j!=n&&l.push(d.substring(j)),j=n);if(!(n=
+l.length))l.push(""),n=1;if(!f||r)return o.length=n,j=function(a){for(var b=0,c=n,d;b<c;b++){if(typeof(d=l[b])=="function")d=d(a),d==null||d==p?d="":typeof d!="string"&&(d=ca(d));o[b]=d}return o.join("")},j.exp=d,j.parts=l,j}var e=b.length,g=a.length;d.startSymbol=function(){return b};d.endSymbol=function(){return a};return d}]}function Fb(b){for(var b=b.split("/"),a=b.length;a--;)b[a]=$a(b[a]);return b.join("/")}function va(b,a){var c=Gb.exec(b),c={protocol:c[1],host:c[3],port:G(c[5])||Hb[c[1]]||
+null,path:c[6]||"/",search:c[8],hash:c[10]};if(a)a.$$protocol=c.protocol,a.$$host=c.host,a.$$port=c.port;return c}function ka(b,a,c){return b+"://"+a+(c==Hb[b]?"":":"+c)}function Fc(b,a,c){var d=va(b);return decodeURIComponent(d.path)!=a||t(d.hash)||d.hash.indexOf(c)!==0?b:ka(d.protocol,d.host,d.port)+a.substr(0,a.lastIndexOf("/"))+d.hash.substr(c.length)}function Gc(b,a,c){var d=va(b);if(decodeURIComponent(d.path)==a)return b;else{var e=d.search&&"?"+d.search||"",g=d.hash&&"#"+d.hash||"",i=a.substr(0,
+a.lastIndexOf("/")),f=d.path.substr(i.length);if(d.path.indexOf(i)!==0)throw A('Invalid url "'+b+'", missing path prefix "'+i+'" !');return ka(d.protocol,d.host,d.port)+a+"#"+c+f+e+g}}function hb(b,a,c){a=a||"";this.$$parse=function(b){var c=va(b,this);if(c.path.indexOf(a)!==0)throw A('Invalid url "'+b+'", missing path prefix "'+a+'" !');this.$$path=decodeURIComponent(c.path.substr(a.length));this.$$search=Ya(c.search);this.$$hash=c.hash&&decodeURIComponent(c.hash)||"";this.$$compose()};this.$$compose=
+function(){var b=pb(this.$$search),c=this.$$hash?"#"+$a(this.$$hash):"";this.$$url=Fb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=ka(this.$$protocol,this.$$host,this.$$port)+a+this.$$url};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Ja(b,a,c){var d;this.$$parse=function(b){var c=va(b,this);if(c.hash&&c.hash.indexOf(a)!==0)throw A('Invalid url "'+b+'", missing hash prefix "'+a+'" !');d=c.path+(c.search?"?"+c.search:"");c=Hc.exec((c.hash||"").substr(a.length));
+this.$$path=c[1]?(c[1].charAt(0)=="/"?"":"/")+decodeURIComponent(c[1]):"";this.$$search=Ya(c[3]);this.$$hash=c[5]&&decodeURIComponent(c[5])||"";this.$$compose()};this.$$compose=function(){var b=pb(this.$$search),c=this.$$hash?"#"+$a(this.$$hash):"";this.$$url=Fb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=ka(this.$$protocol,this.$$host,this.$$port)+d+(this.$$url?"#"+a+this.$$url:"")};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Ib(b,a,c,d){Ja.apply(this,arguments);
+this.$$rewriteAppUrl=function(b){if(b.indexOf(c)==0)return c+d+"#"+a+b.substr(c.length)}}function Ka(b){return function(){return this[b]}}function Jb(b,a){return function(c){if(t(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Ic(){var b="",a=!1;this.hashPrefix=function(a){return u(a)?(b=a,this):b};this.html5Mode=function(b){return u(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function i(a){c.$broadcast("$locationChangeSuccess",
+f.absUrl(),a)}var f,h,k,j=d.url(),l=va(j);a?(h=d.baseHref()||"/",k=h.substr(0,h.lastIndexOf("/")),l=ka(l.protocol,l.host,l.port)+k+"/",f=e.history?new hb(Fc(j,h,b),k,l):new Ib(Gc(j,h,b),b,l,h.substr(k.length+1))):(l=ka(l.protocol,l.host,l.port)+(l.path||"")+(l.search?"?"+l.search:"")+"#"+b+"/",f=new Ja(j,b,l));g.bind("click",function(a){if(!a.ctrlKey&&!(a.metaKey||a.which==2)){for(var b=y(a.target);E(b[0].nodeName)!=="a";)if(b[0]===g[0]||!(b=b.parent())[0])return;var d=b.prop("href"),e=f.$$rewriteAppUrl(d);
+d&&!b.attr("target")&&e&&(f.$$parse(e),c.$apply(),a.preventDefault(),T.angular["ff-684208-preventDefault"]=!0)}});f.absUrl()!=j&&d.url(f.absUrl(),!0);d.onUrlChange(function(a){f.absUrl()!=a&&(c.$evalAsync(function(){var b=f.absUrl();f.$$parse(a);i(b)}),c.$$phase||c.$digest())});var n=0;c.$watch(function(){var a=d.url();if(!n||a!=f.absUrl())n++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",f.absUrl(),a).defaultPrevented?f.$$parse(a):(d.url(f.absUrl(),f.$$replace),f.$$replace=!1,i(a))});
+return n});return f}]}function Jc(){this.$get=["$window",function(b){function a(a){a instanceof A&&(a.stack?a=a.message&&a.stack.indexOf(a.message)===-1?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function c(c){var e=b.console||{},g=e[c]||e.log||D;return g.apply?function(){var b=[];m(arguments,function(c){b.push(a(c))});return g.apply(e,b)}:function(a,b){g(a,b)}}return{log:c("log"),warn:c("warn"),info:c("info"),error:c("error")}}]}function Kc(b,
+a){function c(a){return a.indexOf(q)!=-1}function d(){return o+1<b.length?b.charAt(o+1):!1}function e(a){return"0"<=a&&a<="9"}function g(a){return a==" "||a=="\r"||a=="\t"||a=="\n"||a=="\u000b"||a=="\u00a0"}function i(a){return"a"<=a&&a<="z"||"A"<=a&&a<="Z"||"_"==a||a=="$"}function f(a){return a=="-"||a=="+"||e(a)}function h(a,c,d){d=d||o;throw A("Lexer Error: "+a+" at column"+(u(c)?"s "+c+"-"+o+" ["+b.substring(c,d)+"]":" "+d)+" in expression ["+b+"].");}function k(){for(var a="",c=o;o<b.length;){var g=
+E(b.charAt(o));if(g=="."||e(g))a+=g;else{var k=d();if(g=="e"&&f(k))a+=g;else if(f(g)&&k&&e(k)&&a.charAt(a.length-1)=="e")a+=g;else if(f(g)&&(!k||!e(k))&&a.charAt(a.length-1)=="e")h("Invalid exponent");else break}o++}a*=1;n.push({index:c,text:a,json:!0,fn:function(){return a}})}function j(){for(var c="",d=o,f,k,h;o<b.length;){var j=b.charAt(o);if(j=="."||i(j)||e(j))j=="."&&(f=o),c+=j;else break;o++}if(f)for(k=o;k<b.length;){j=b.charAt(k);if(j=="("){h=c.substr(f-d+1);c=c.substr(0,f-d);o=k;break}if(g(j))k++;
+else break}d={index:d,text:c};if(La.hasOwnProperty(c))d.fn=d.json=La[c];else{var l=Kb(c,a);d.fn=x(function(a,b){return l(a,b)},{assign:function(a,b){return Lb(a,c,b)}})}n.push(d);h&&(n.push({index:f,text:".",json:!1}),n.push({index:f+1,text:h,json:!1}))}function l(a){var c=o;o++;for(var d="",e=a,f=!1;o<b.length;){var g=b.charAt(o);e+=g;if(f)g=="u"?(g=b.substring(o+1,o+5),g.match(/[\da-f]{4}/i)||h("Invalid unicode escape [\\u"+g+"]"),o+=4,d+=String.fromCharCode(parseInt(g,16))):(f=Lc[g],d+=f?f:g),
+f=!1;else if(g=="\\")f=!0;else if(g==a){o++;n.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}else d+=g;o++}h("Unterminated quote",c)}for(var n=[],r,o=0,w=[],q,v=":";o<b.length;){q=b.charAt(o);if(c("\"'"))l(q);else if(e(q)||c(".")&&e(d()))k();else if(i(q)){if(j(),"{,".indexOf(v)!=-1&&w[0]=="{"&&(r=n[n.length-1]))r.json=r.text.indexOf(".")==-1}else if(c("(){}[].,;:"))n.push({index:o,text:q,json:":[,".indexOf(v)!=-1&&c("{[")||c("}]:,")}),c("{[")&&w.unshift(q),c("}]")&&w.shift(),
+o++;else if(g(q)){o++;continue}else{var m=q+d(),B=La[q],z=La[m];z?(n.push({index:o,text:m,fn:z}),o+=2):B?(n.push({index:o,text:q,fn:B,json:"[,:".indexOf(v)!=-1&&c("+-")}),o+=1):h("Unexpected next character ",o,o+1)}v=q}return n}function Mc(b,a,c,d){function e(a,c){throw A("Syntax Error: Token '"+c.text+"' "+a+" at column "+(c.index+1)+" of the expression ["+b+"] starting at ["+b.substring(c.index)+"].");}function g(){if(N.length===0)throw A("Unexpected end of expression: "+b);return N[0]}function i(a,
+b,c,d){if(N.length>0){var f=N[0],e=f.text;if(e==a||e==b||e==c||e==d||!a&&!b&&!c&&!d)return f}return!1}function f(b,c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),N.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function k(a,b){return function(c,d){return a(c,d,b)}}function j(a,b,c){return function(d,f){return b(d,f,a,c)}}function l(){for(var a=[];;)if(N.length>0&&!i("}",")",";","]")&&a.push(t()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,
+f=0;f<a.length;f++){var e=a[f];e&&(d=e(b,c))}return d}}function n(){for(var a=f(),b=c(a.text),d=[];;)if(a=f(":"))d.push(H());else{var e=function(a,c,f){for(var f=[f],e=0;e<d.length;e++)f.push(d[e](a,c));return b.apply(a,f)};return function(){return e}}}function r(){for(var a=o(),b;;)if(b=f("||"))a=j(a,b.fn,o());else return a}function o(){var a=w(),b;if(b=f("&&"))a=j(a,b.fn,o());return a}function w(){var a=q(),b;if(b=f("==","!="))a=j(a,b.fn,w());return a}function q(){var a;a=v();for(var b;b=f("+",
+"-");)a=j(a,b.fn,v());if(b=f("<",">","<=",">="))a=j(a,b.fn,q());return a}function v(){for(var a=m(),b;b=f("*","/","%");)a=j(a,b.fn,m());return a}function m(){var a;return f("+")?B():(a=f("-"))?j(V,a.fn,m()):(a=f("!"))?k(a.fn,m()):B()}function B(){var a;if(f("("))a=t(),h(")");else if(f("["))a=z();else if(f("{"))a=L();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=ea(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");
+return a}function z(){var a=[];if(g().text!="]"){do a.push(H());while(f(","))}h("]");return function(b,c){for(var d=[],f=0;f<a.length;f++)d.push(a[f](b,c));return d}}function L(){var a=[];if(g().text!="}"){do{var b=f(),b=b.string||b.text;h(":");var c=H();a.push({key:b,value:c})}while(f(","))}h("}");return function(b,c){for(var d={},f=0;f<a.length;f++){var e=a[f],g=e.value(b,c);d[e.key]=g}return d}}var V=J(0),s,N=Kc(b,d),H=function(){var a=r(),c,d;return(d=f("="))?(a.assign||e("implies assignment but ["+
+b.substring(0,d.index)+"] can not be assigned to",d),c=r(),function(b,d){return a.assign(b,c(b,d),d)}):a},y=function(a,b){var c=[];if(g().text!=")"){do c.push(H());while(f(","))}h(")");return function(d,f){for(var e=[],g=b?b(d,f):d,k=0;k<c.length;k++)e.push(c[k](d,f));k=a(d,f)||D;return k.apply?k.apply(g,e):k(e[0],e[1],e[2],e[3],e[4])}},u=function(a){var b=f().text,c=Kb(b,d);return x(function(b,d){return c(a(b,d),d)},{assign:function(c,d,f){return Lb(a(c,f),b,d)}})},ea=function(a){var b=H();h("]");
+return x(function(c,d){var f=a(c,d),e=b(c,d),g;if(!f)return p;if((f=f[e])&&f.then){g=f;if(!("$$v"in f))g.$$v=p,g.then(function(a){g.$$v=a});f=f.$$v}return f},{assign:function(c,d,f){return a(c,f)[b(c,f)]=d}})},t=function(){for(var a=H(),b;;)if(b=f("|"))a=j(a,b.fn,n());else return a};a?(H=r,y=u=ea=t=function(){e("is not valid json",{text:b,index:0})},s=B()):s=l();N.length!==0&&e("is an unexpected token",N[0]);return s}function Lb(b,a,c){for(var a=a.split("."),d=0;a.length>1;d++){var e=a.shift(),g=
+b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;i<g;i++)d=a[i],b&&(b=(e=b)[d]);return!c&&M(b)?Wa(e,b):b}function Mb(b,a,c,d,e){return function(g,i){var f=i&&i.hasOwnProperty(b)?i:g,h;if(f===null||f===p)return f;if((f=f[b])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!a||f===null||f===p)return f;if((f=f[a])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!c||f===
+null||f===p)return f;if((f=f[c])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!d||f===null||f===p)return f;if((f=f[d])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!e||f===null||f===p)return f;if((f=f[e])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}return f}}function Kb(b,a){if(ib.hasOwnProperty(b))return ib[b];var c=b.split("."),d=c.length,e;if(a)e=d<6?Mb(c[0],c[1],c[2],c[3],c[4]):function(a,b){var e=0,
+g;do g=Mb(c[e++],c[e++],c[e++],c[e++],c[e++])(a,b),b=p,a=g;while(e<d);return g};else{var g="var l, fn, p;\n";m(c,function(a,b){g+="if(s === null || s === undefined) return s;\nl=s;\ns="+(b?"s":'((k&&k.hasOwnProperty("'+a+'"))?k:s)')+'["'+a+'"];\nif (s && s.then) {\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n'});g+="return s;";e=Function("s","k",g);e.toString=function(){return g}}return ib[b]=e}function Nc(){var b={};this.$get=["$filter","$sniffer",
+function(a,c){return function(d){switch(typeof d){case "string":return b.hasOwnProperty(d)?b[d]:b[d]=Mc(d,!1,a,c.csp);case "function":return d;default:return D}}}]}function Oc(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Pc(function(a){b.$evalAsync(a)},a)}]}function Pc(b,a){function c(a){return a}function d(a){return i(a)}var e=function(){var f=[],h,k;return k={resolve:function(a){if(f){var c=f;f=p;h=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],h.then(a[0],
+a[1])})}},reject:function(a){k.resolve(i(a))},promise:{then:function(b,g){var k=e(),i=function(d){try{k.resolve((b||c)(d))}catch(f){a(f),k.reject(f)}},o=function(b){try{k.resolve((g||d)(b))}catch(c){a(c),k.reject(c)}};f?f.push([i,o]):h.then(i,o);return k.promise}}}},g=function(a){return a&&a.then?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},i=function(a){return{then:function(c,g){var j=e();b(function(){j.resolve((g||d)(a))});return j.promise}}};return{defer:e,reject:i,
+when:function(f,h,k){var j=e(),l,n=function(b){try{return(h||c)(b)}catch(d){return a(d),i(d)}},r=function(b){try{return(k||d)(b)}catch(c){return a(c),i(c)}};b(function(){g(f).then(function(a){l||(l=!0,j.resolve(g(a).then(n,r)))},function(a){l||(l=!0,j.resolve(r(a)))})});return j.promise},all:function(a){var b=e(),c=a.length,d=[];c?m(a,function(a,e){g(a).then(function(a){e in d||(d[e]=a,--c||b.resolve(d))},function(a){e in d||b.reject(a)})}):b.resolve(d);return b.promise}}}function Qc(){var b={};this.when=
+function(a,c){b[a]=x({reloadOnSearch:!0},c);if(a){var d=a[a.length-1]=="/"?a.substr(0,a.length-1):a+"/";b[d]={redirectTo:a}}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache",function(a,c,d,e,g,i,f){function h(){var b=k(),h=r.current;if(b&&h&&b.$route===h.$route&&ga(b.pathParams,h.pathParams)&&!b.reloadOnSearch&&!n)h.params=b.params,U(h.params,d),a.$broadcast("$routeUpdate",h);else if(b||
+h)n=!1,a.$broadcast("$routeChangeStart",b,h),(r.current=b)&&b.redirectTo&&(F(b.redirectTo)?c.path(j(b.redirectTo,b.params)).search(b.params).replace():c.url(b.redirectTo(b.pathParams,c.path(),c.search())).replace()),e.when(b).then(function(){if(b){var a=[],c=[],d;m(b.resolve||{},function(b,d){a.push(d);c.push(M(b)?g.invoke(b):g.get(b))});if(!u(d=b.template))if(u(d=b.templateUrl))d=i.get(d,{cache:f}).then(function(a){return a.data});u(d)&&(a.push("$template"),c.push(d));return e.all(c).then(function(b){var c=
+{};m(b,function(b,d){c[a[d]]=b});return c})}}).then(function(c){if(b==r.current){if(b)b.locals=c,U(b.params,d);a.$broadcast("$routeChangeSuccess",b,h)}},function(c){b==r.current&&a.$broadcast("$routeChangeError",b,h,c)})}function k(){var a,d;m(b,function(b,e){if(!d&&(a=l(c.path(),e)))d=ya(b,{params:x({},c.search(),a),pathParams:a}),d.$route=b});return d||b[null]&&ya(b[null],{params:{},pathParams:{}})}function j(a,b){var c=[];m((a||"").split(":"),function(a,d){if(d==0)c.push(a);else{var e=a.match(/(\w+)(.*)/),
+f=e[1];c.push(b[f]);c.push(e[2]||"");delete b[f]}});return c.join("")}var l=function(a,b){var c="^"+b.replace(/([\.\\\(\)\^\$])/g,"\\$1")+"$",d=[],e={};m(b.split(/\W/),function(a){if(a){var b=RegExp(":"+a+"([\\W])");c.match(b)&&(c=c.replace(b,"([^\\/]*)$1"),d.push(a))}});var f=a.match(RegExp(c));f&&m(d,function(a,b){e[a]=f[b+1]});return f?e:null},n=!1,r={routes:b,reload:function(){n=!0;a.$evalAsync(h)}};a.$on("$locationChangeSuccess",h);return r}]}function Rc(){this.$get=J({})}function Sc(){var b=
+10;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse",function(a,c,d){function e(){this.$id=xa();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$asyncQueue=[];this.$$listeners={}}function g(a){if(h.$$phase)throw A(h.$$phase+" already in progress");h.$$phase=a}function i(a,b){var c=d(a);ra(c,b);return c}function f(){}e.prototype={$new:function(a){if(M(a))throw A("API-CHANGE: Use $controller to instantiate controllers.");
+a?(a=new e,a.$root=this.$root):(a=function(){},a.prototype=this,a=new a,a.$id=xa());a["this"]=a;a.$$listeners={};a.$parent=this;a.$$asyncQueue=[];a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=i(a,"watch"),e=this.$$watchers,g={fn:b,last:f,get:d,exp:a,eq:!!c};if(!M(b)){var h=i(b||D,"listener");g.fn=function(a,b,
+c){h(c)}}if(!e)e=this.$$watchers=[];e.unshift(g);return function(){za(e,g)}},$digest:function(){var a,d,e,i,r,o,m,q=b,v,p=[],B,z;g("$digest");do{m=!1;v=this;do{for(r=v.$$asyncQueue;r.length;)try{v.$eval(r.shift())}catch(L){c(L)}if(i=v.$$watchers)for(o=i.length;o--;)try{if(a=i[o],(d=a.get(v))!==(e=a.last)&&!(a.eq?ga(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))m=!0,a.last=a.eq?U(d):d,a.fn(d,e===f?d:e,v),q<5&&(B=4-q,p[B]||(p[B]=[]),z=M(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):
+a.exp,z+="; newVal: "+ca(d)+"; oldVal: "+ca(e),p[B].push(z))}catch(V){c(V)}if(!(i=v.$$childHead||v!==this&&v.$$nextSibling))for(;v!==this&&!(i=v.$$nextSibling);)v=v.$parent}while(v=i);if(m&&!q--)throw h.$$phase=null,A(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+ca(p));}while(m||r.length);h.$$phase=null},$destroy:function(){if(h!=this){var a=this.$parent;this.$broadcast("$destroy");if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==
+this)a.$$childTail=this.$$prevSibling;if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{h.$$phase=null;try{h.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);return function(){za(c,
+b)}},$emit:function(a,b){var d=[],e,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},i=[h].concat(ha.call(arguments,1)),m,p;do{e=f.$$listeners[a]||d;h.currentScope=f;m=0;for(p=e.length;m<p;m++)try{if(e[m].apply(null,i),g)return h}catch(B){c(B)}f=f.$parent}while(f);return h},$broadcast:function(a,b){var d=this,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},
+g=[f].concat(ha.call(arguments,1));do if(d=e,f.currentScope=d,m(d.$$listeners[a],function(a){try{a.apply(null,g)}catch(b){c(b)}}),!(e=d.$$childHead||d!==this&&d.$$nextSibling))for(;d!==this&&!(e=d.$$nextSibling);)d=d.$parent;while(d=e);return f}};var h=new e;return h}]}function Tc(){this.$get=["$window",function(b){var a={},c=G((/android (\d+)/.exec(E(b.navigator.userAgent))||[])[1]);return{history:!(!b.history||!b.history.pushState||c<4),hashchange:"onhashchange"in b&&(!b.document.documentMode||
+b.document.documentMode>7),hasEvent:function(c){if(c=="input"&&$==9)return!1;if(t(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=J(T)}function Nb(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=E(Q(b.substr(0,e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Ob(b){var a=I(b)?b:p;return function(c){a||(a=Nb(b));return c?a[E(c)]||null:a}}function Pb(b,a,c){if(M(c))return c(b,a);m(c,
+function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){F(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ob(d,!0)));return d}],transformRequest:[function(a){return I(a)&&Ta.apply(a)!=="[object File]"?ca(a):a}],headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},
+e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,h,k,j){function l(a){function c(a){var b=x({},a,{data:Pb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:k.reject(b)}a.method=la(a.method);var e=a.transformRequest||d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=x({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[E(a.method)],a.headers),e=Pb(a.data,Ob(g),e),h;t(a.data)&&delete g["Content-Type"];
+h=n(a,e,g);h=h.then(c,c);m(w,function(a){h=a(h)});h.success=function(b){h.then(function(c){b(c.data,c.status,c.headers,a)});return h};h.error=function(b){h.then(null,function(c){b(c.data,c.status,c.headers,a)});return h};return h}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(w,[a,b,Nb(c)]):m.remove(w));f(b,a,c);h.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?j.resolve:j.reject)({data:a,status:c,headers:Ob(d),config:b})}function i(){var a=Va(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,
+1)}var j=k.defer(),n=j.promise,m,p,w=r(b.url,b.params);l.pendingRequests.push(b);n.then(i,i);b.cache&&b.method=="GET"&&(m=I(b.cache)?b.cache:o);if(m)if(p=m.get(w))if(p.then)return p.then(i,i),p;else K(p)?f(p[1],p[0],U(p[2])):f(p,200,{});else m.put(w,n);p||a(b.method,w,c,e,d,b.timeout,b.withCredentials);return n}function r(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==p||(I(a)&&(a=ca(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+
+c.join("&")}var o=c("$http"),w=[];m(e,function(a){w.push(F(a)?j.get(a):j.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(x(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]=function(b,c,d){return l(x(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Wc(){this.$get=["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0],
+a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;$?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c)}return function(e,h,k,j,l,n,r){function o(a,c,d,e){c=(h.match(Gb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(D)}b.$$incOutstandingRequestCount();h=h||b.url();
+if(E(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};i(h.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?o(j,200,d[p].data):o(j,-2);delete d[p]})}else{var q=new a;q.open(e,h,!0);m(l,function(a,b){a&&q.setRequestHeader(b,a)});var v;q.onreadystatechange=function(){q.readyState==4&&o(j,v||q.status,q.responseText,q.getAllResponseHeaders())};if(r)q.withCredentials=!0;q.send(k||"");n>0&&c(function(){v=-1;q.abort()},n)}}}function Zc(){this.$get=function(){return{id:"en-us",
+NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),
+SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var k=c.defer(),j=k.promise,l=u(h)&&!h,f=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),
+d(a)}l||b.$apply()},f),h=function(){delete g[j.$$timeoutId]};j.$$timeoutId=f;g[f]=k;j.then(h,h);return j}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Qb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Rb);a("date",Sb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number",
+Tb);a("orderBy",Ub);a("uppercase",ed)}function ad(){return function(b,a){if(!(b instanceof Array))return b;var c=[];c.check=function(a){for(var b=0;b<c.length;b++)if(!c[b](a))return!1;return!0};var d=function(a,b){if(b.charAt(0)==="!")return!d(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return(""+a).toLowerCase().indexOf(b)>-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c<a.length;c++)if(d(a[c],b))return!0;return!1;
+default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var e in a)e=="$"?function(){var b=(""+a[e]).toLowerCase();b&&c.push(function(a){return d(a,b)})}():function(){var b=e,f=(""+a[e]).toLowerCase();f&&c.push(function(a){return d(gb(a,b),f)})}();break;case "function":c.push(a);break;default:return b}for(var g=[],i=0;i<b.length;i++){var f=b[i];c.check(f)&&g.push(f)}return g}}function Rb(b){var a=b.NUMBER_FORMATS;return function(b,d){if(t(d))d=a.CURRENCY_SYM;
+return Vb(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Tb(b){var a=b.NUMBER_FORMATS;return function(b,d){return Vb(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Vb(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=b<0,b=Math.abs(b),i=b+"",f="",h=[];if(i.indexOf("e")!==-1)f=i;else{i=(i.split(Wb)[1]||"").length;t(e)&&(e=Math.min(Math.max(a.minFrac,i),a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(Wb),i=b[0],b=b[1]||"",k=0,j=a.lgSize,l=a.gSize;
+if(i.length>=j+l)for(var k=i.length-j,n=0;n<k;n++)(k-n)%l===0&&n!==0&&(f+=c),f+=i.charAt(n);for(n=k;n<i.length;n++)(i.length-n)%j===0&&n!==0&&(f+=c),f+=i.charAt(n);for(;b.length<e;)b+="0";e&&(f+=d+b.substr(0,e))}h.push(g?a.negPre:a.posPre);h.push(f);h.push(g?a.negSuf:a.posSuf);return h.join("")}function jb(b,a,c){var d="";b<0&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function O(b,a,c,d){return function(e){e=e["get"+b]();if(c>0||e>-c)e+=c;e===0&&c==-12&&(e=
+12);return jb(e,a,d)}}function Ma(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Sb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0;b[9]&&(g=G(b[9]+b[10]),i=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-i,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;return function(c,e){var g="",i=[],f,h,e=e||
+"mediumDate",e=b.DATETIME_FORMATS[e]||e;F(c)&&(c=fd.test(c)?G(c):a(c));wa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(h=gd.exec(e))?(i=i.concat(ha.call(h,1)),e=i.pop()):(i.push(e),e=null);m(i,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return ca(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?a=b.length:a<
+-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Ub(b){return function(a,c,d){function e(a,b){return Xa(b)?function(b,c){return a(c,b)}:a}if(!(a instanceof Array))return a;if(!c)return a;for(var c=K(c)?c:[c],c=Ua(c,function(a){var c=!1,d=a||ma;if(F(a)){if(a.charAt(0)=="+"||a.charAt(0)=="-")c=a.charAt(0)=="-",a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?(f=="string"&&(c=c.toLowerCase()),
+f=="string"&&(e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)}),g=[],i=0;i<a.length;i++)g.push(a[i]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(e!==0)return e}return 0},d))}}function R(b){M(b)&&(b={link:b});b.restrict=b.restrict||"AC";return J(b)}function Xb(b,a){function c(a,c){c=c?"-"+ab(c,"-"):"";b.removeClass((a?Na:Oa)+c).addClass((a?Oa:Na)+c)}var d=this,e=b.parent().controller("form")||Pa,g=0,i=d.$error={};d.$name=a.name;d.$dirty=!1;d.$pristine=
+!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(Qa);c(!0);d.$addControl=function(a){a.$name&&!d.hasOwnProperty(a.$name)&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];m(i,function(b,c){d.$setValidity(c,!0,a)})};d.$setValidity=function(a,b,k){var j=i[a];if(b){if(j&&(za(j,k),!j.length)){g--;if(!g)c(b),d.$valid=!0,d.$invalid=!1;i[a]=!1;c(!0,a);e.$setValidity(a,!0,d)}}else{g||c(b);if(j){if(Va(j,k)!=-1)return}else i[a]=j=[],g++,c(!1,a),e.$setValidity(a,
+!1,d);j.push(k);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(Qa).addClass(Yb);d.$dirty=!0;d.$pristine=!1}}function S(b){return t(b)||b===""||b===null||b!==b}function Ra(b,a,c,d,e,g){var i=function(){var c=Q(a.val());d.$viewValue!==c&&b.$apply(function(){d.$setViewValue(c)})};if(e.hasEvent("input"))a.bind("input",i);else{var f;a.bind("keydown",function(a){a=a.keyCode;a===91||15<a&&a<19||37<=a&&a<=40||f||(f=g.defer(function(){i();f=null}))});a.bind("change",i)}d.$render=function(){a.val(S(d.$viewValue)?
+"":d.$viewValue)};var h=c.ngPattern,k=function(a,b){return S(b)||a.test(b)?(d.$setValidity("pattern",!0),b):(d.$setValidity("pattern",!1),p)};h&&(h.match(/^\/(.*)\/$/)?(h=RegExp(h.substr(1,h.length-2)),e=function(a){return k(h,a)}):e=function(a){var c=b.$eval(h);if(!c||!c.test)throw new A("Expected "+h+" to be a RegExp but was "+c);return k(c,a)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var j=G(c.ngMinlength),e=function(a){return!S(a)&&a.length<j?(d.$setValidity("minlength",!1),
+p):(d.$setValidity("minlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var l=G(c.ngMaxlength),c=function(a){return!S(a)&&a.length>l?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return R(function(c,d,e){c.$watch(e[b],function(b,e){if(a===!0||c.$index%2===a)e&&b!==e&&(I(e)&&!K(e)&&(e=Ua(e,function(a,b){if(a)return b})),d.removeClass(K(e)?e.join(" "):e)),I(b)&&!K(b)&&(b=Ua(b,
+function(a,b){if(a)return b})),b&&d.addClass(K(b)?b.join(" "):b)},!0)})}var E=function(b){return F(b)?b.toLowerCase():b},la=function(b){return F(b)?b.toUpperCase():b},A=T.Error,$=G((/msie (\d+)/.exec(E(navigator.userAgent))||[])[1]),y,ia,ha=[].slice,Sa=[].push,Ta=Object.prototype.toString,Zb=T.angular||(T.angular={}),ta,Db,Z=["0","0","0"];D.$inject=[];ma.$inject=[];Db=$<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?
+b.nodeName:b[0].nodeName};var lc=/[A-Z]/g,id={full:"1.0.2",major:1,minor:0,dot:2,codeName:"debilitating-awesomeness"},Ba=P.cache={},Aa=P.expando="ng-"+(new Date).getTime(),pc=1,$b=T.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},eb=T.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},nc=/([\:\-\_]+(.))/g,oc=/^moz([A-Z])/,ua=P.prototype={ready:function(b){function a(){c||
+(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);P(T).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?y(this[b]):y(this[this.length+b])},length:0,push:Sa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[E(b)]=b});var Ab={};m("input,select,option,textarea,button,form".split(","),function(b){Ab[la(b)]=!0});m({data:vb,inheritedData:Da,scope:function(b){return Da(b,
+"$scope")},controller:yb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=sb(a);if(u(c))b.style[a]=c;else{var d;$<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];$<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=E(a);if(Ea[d])if(u(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||D).specified?d:p;else if(u(c))b.setAttribute(a,
+c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(u(c))b[a]=c;else return b[a]},text:x($<9?function(b,a){if(b.nodeType==1){if(t(a))return b.innerText;b.innerText=a}else{if(t(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(t(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(t(a))return b.value;b.value=a},html:function(b,a){if(t(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)sa(d[c]);b.innerHTML=a}},function(b,
+a){P.prototype[a]=function(a,d){var e,g;if((b.length==2&&b!==Ca&&b!==yb?a:d)===p)if(I(a)){for(e=0;e<this.length;e++)if(b===vb)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}else{if(this.length)return b(this[0],a,d)}else{for(e=0;e<this.length;e++)b(this[e],a,d);return this}return b.$dv}});m({removeData:tb,dealoc:sa,bind:function a(c,d,e){var g=da(c,"events"),i=da(c,"handle");g||da(c,"events",g={});i||da(c,"handle",i=qc(c,g));m(d.split(" "),function(d){var h=g[d];if(!h){if(d=="mouseenter"||
+d=="mouseleave"){var k=0;g.mouseenter=[];g.mouseleave=[];a(c,"mouseover",function(a){k++;k==1&&i(a,"mouseenter")});a(c,"mouseout",function(a){k--;k==0&&i(a,"mouseleave")})}else $b(c,d,i),g[d]=[];h=g[d]}h.push(e)})},unbind:ub,replaceWith:function(a,c){var d,e=a.parentNode;sa(a);m(new P(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];m(a.childNodes,function(a){a.nodeName!="#text"&&c.push(a)});return c},contents:function(a){return a.childNodes},
+append:function(a,c){m(new P(c),function(c){a.nodeType===1&&a.appendChild(c)})},prepend:function(a,c){if(a.nodeType===1){var d=a.firstChild;m(new P(c),function(c){d?a.insertBefore(c,d):(a.appendChild(c),d=c)})}},wrap:function(a,c){var c=y(c)[0],d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){sa(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;m(new P(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:xb,removeClass:wb,toggleClass:function(a,
+c,d){t(d)&&(d=!Ca(a,c));(d?xb:wb)(a,c)},parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},next:function(a){return a.nextSibling},find:function(a,c){return a.getElementsByTagName(c)},clone:db},function(a,c){P.prototype[c]=function(c,e){for(var g,i=0;i<this.length;i++)g==p?(g=a(this[i],c,e),g!==p&&(g=y(g))):cb(g,a(this[i],c,e));return g==p?this:g}});Fa.prototype={put:function(a,c){this[ja(a)]=c},get:function(a){return this[ja(a)]},remove:function(a){var c=this[a=ja(a)];delete this[a];
+return c}};fb.prototype={push:function(a,c){var d=this[a=ja(a)];d?d.push(c):this[a]=[c]},shift:function(a){var c=this[a=ja(a)];if(c)return c.length==1?(delete this[a],c[0]):c.shift()}};var sc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,tc=/,/,uc=/^\s*(_?)(.+?)\1\s*$/,rc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Eb="Non-assignable model expression: ";Cb.$inject=["$provide"];var Ac=/^(x[\:\-_]|data[\:\-_])/i,Gb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,ac=/^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
+Hc=ac,Hb={http:80,https:443,ftp:21};hb.prototype={$$replace:!1,absUrl:Ka("$$absUrl"),url:function(a,c){if(t(a))return this.$$url;var d=ac.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Ka("$$protocol"),host:Ka("$$host"),port:Ka("$$port"),path:Jb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(t(a))return this.$$search;u(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=
+F(a)?Ya(a):a;this.$$compose();return this},hash:Jb("$$hash",ma),replace:function(){this.$$replace=!0;return this}};Ja.prototype=ya(hb.prototype);Ib.prototype=ya(Ja.prototype);var La={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:D,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return(u(d)?d:0)+(u(e)?e:0)},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(u(d)?d:0)-(u(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/
+e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":D,"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&
+e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Yc=T.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw new A("This browser does not support XMLHttpRequest.");};Qb.$inject=["$provide"];Rb.$inject=["$locale"];Tb.$inject=["$locale"];
+var Wb=".",hd={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ma("Month"),MMM:Ma("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ma("Day"),EEE:Ma("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=a.getTimezoneOffset();return jb(a/60,2)+jb(Math.abs(a%60),2)}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
+fd=/^\d+$/;Sb.$inject=["$locale"];var dd=J(E),ed=J(la);Ub.$inject=["$parse"];var jd=J({restrict:"E",compile:function(a,c){c.href||c.$set("href","");return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a,c){var d=fa("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=fa("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,
+e,g){g.$observe(c,function(c){g.$set(a,c);$&&e.prop(a,c)})}}}});var Pa={$addControl:D,$removeControl:D,$setValidity:D,$setDirty:D};Xb.$inject=["$element","$attrs","$scope"];var Sa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:Xb,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",h);d.bind("$destroy",function(){c(function(){eb(d[0],"submit",h)},0,!1)})}var k=d.parent().controller("form"),
+j=i.name||i.ngForm;j&&(a[j]=f);k&&d.bind("$destroy",function(){k.$removeControl(f);j&&(a[j]=p);x(f,Pa)})}}}};return a?x(U(d),{restrict:"EAC"}):d}]},kd=Sa(),ld=Sa(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Ra,number:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);e.$parsers.push(function(a){var c=S(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""?
+null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return S(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!S(a)&&a<f?(e.$setValidity("min",!1),p):(e.$setValidity("min",!0),a)};e.$parsers.push(a);e.$formatters.push(a)}if(d.max){var h=parseFloat(d.max),d=function(a){return!S(a)&&a>h?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return S(a)||wa(a)?(e.$setValidity("number",
+!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){t(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&
+a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;F(g)||(g=!0);F(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:D,button:D,submit:D,reset:D},cc=["$browser","$sniffer",
+function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(bc[E(g.type)]||bc.text)(d,e,g,i,c,a)}}}],Oa="ng-valid",Na="ng-invalid",Qa="ng-pristine",Yb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+ab(c,"-"):"";e.removeClass((a?Na:Oa)+c).addClass((a?Oa:Na)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=
+!0;this.$invalid=!1;this.$name=d.name;var g=g(d.ngModel),f=g.assign;if(!f)throw A(Eb+d.ngModel+" ("+pa(e)+")");this.$render=D;var h=e.inheritedData("$formController")||Pa,k=0,j=this.$error={};e.addClass(Qa);i(!0);this.$setValidity=function(a,c){if(j[a]!==!c){if(c){if(j[a]&&k--,!k)i(!0),this.$valid=!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,k++;j[a]=!c;i(c,a);h.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=
+!1,e.removeClass(Qa).addClass(Yb),h.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,f(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(g,function(a){if(l.$modelValue!==a){var c=l.$formatters,d=c.length;for(l.$modelValue=a;d--;)a=c[d](a);if(l.$viewValue!==a)l.$viewValue=a,l.$render()}})}],qd=function(){return{require:["ngModel","^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],i=e[1]||Pa;i.$addControl(g);
+c.bind("$destroy",function(){i.$removeControl(g)})}}},rd=J({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(S(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel",
+link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return K(a)?a.join(", "):p})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=R(function(a,c,d){c.addClass("ng-binding").data("$binding",
+d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],yd=kb("",!0),zd=kb("Odd",0),Ad=kb("Even",1),Bd=R({compile:function(a,c){c.$set("ngCloak",p);
+a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),function(a){var c=fa("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(E(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Ed=R(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),
+Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,i){var f=i.ngInclude||i.src,h=i.onload||"",k=i.autoscroll;return function(g,i){var n=0,m,o=function(){m&&(m.$destroy(),m=null);i.html("")};g.$watch(f,function(f){var q=++n;f?a.get(f,{cache:c}).success(function(a){q===n&&(m&&m.$destroy(),m=g.$new(),i.html(a),e(i.contents())(m),u(k)&&(!k||g.$eval(k))&&d(),m.$emit("$includeContentLoaded"),g.$eval(h))}).error(function(){q===n&&
+o()}):o()})}}}}],Gd=R({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=R({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),k=i.offset||0,j=e.$eval(h),l={},n=c.startSymbol(),r=c.endSymbol();m(j,function(a,e){l[e]=c(a.replace(d,n+f+"-"+k+r))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(j[c]||(c=a.pluralCat(c-k)),l[c](e,g,!0))},function(a){g.text(a)})}}}],
+Jd=R({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,i){var f=i.ngRepeat,i=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),h,k,j;if(!i)throw A("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=i[1];h=i[2];i=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!i)throw A("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");k=i[3]||i[1];j=i[2];var l=new fb;a.$watch(function(a){var e,f,i=a.$eval(h),m=hc(i,
+!0),p,y=new fb,B,z,t,u,s=c;if(K(i))t=i||[];else{t=[];for(B in i)i.hasOwnProperty(B)&&B.charAt(0)!="$"&&t.push(B);t.sort()}e=0;for(f=t.length;e<f;e++){B=i===t?e:t[e];z=i[B];if(u=l.shift(z)){p=u.scope;y.push(z,u);if(e!==u.index)u.index=e,s.after(u.element);s=u.element}else p=a.$new();p[k]=z;j&&(p[j]=B);p.$index=e;p.$first=e===0;p.$last=e===m-1;p.$middle=!(p.$first||p.$last);u||d(p,function(a){s.after(a);u={scope:p,element:s=a,index:e};y.push(z,u)})}for(B in l)if(l.hasOwnProperty(B))for(t=l[B];t.length;)z=
+t.pop(),z.element.remove(),z.scope.$destroy();l=y})}}}),Kd=R(function(a,c,d){a.$watch(d.ngShow,function(a){c.css("display",Xa(a)?"":"none")})}),Ld=R(function(a,c,d){a.$watch(d.ngHide,function(a){c.css("display",Xa(a)?"none":"")})}),Md=R(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&m(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Nd=J({restrict:"EA",compile:function(a,c){var d=c.ngSwitch||c.on,e={};a.data("ng-switch",e);return function(a,i){var f,h,k;a.$watch(d,function(d){h&&(k.$destroy(),
+h.remove(),h=k=null);if(f=e["!"+d]||e["?"])a.$eval(c.change),k=a.$new(),f(k,function(a){h=a;i.append(a)})})}}}),Od=R({transclude:"element",priority:500,compile:function(a,c,d){a=a.inheritedData("ng-switch");qa(a);a["!"+c.ngSwitchWhen]=d}}),Pd=R({transclude:"element",priority:500,compile:function(a,c,d){a=a.inheritedData("ng-switch");qa(a);a["?"]=d}}),Qd=R({controller:["$transclude","$element",function(a,c){a(function(a){c.append(a)})}]}),Rd=["$http","$templateCache","$route","$anchorScroll","$compile",
+"$controller",function(a,c,d,e,g,i){return{restrict:"ECA",terminal:!0,link:function(a,c,k){function j(){var j=d.current&&d.current.locals,k=j&&j.$template;if(k){c.html(k);l&&(l.$destroy(),l=null);var k=g(c.contents()),m=d.current;l=m.scope=a.$new();if(m.controller)j.$scope=l,j=i(m.controller,j),c.contents().data("$ngControllerController",j);k(l);l.$emit("$viewContentLoaded");l.$eval(n);e()}else c.html(""),l&&(l.$destroy(),l=null)}var l,n=k.onload||"";a.$on("$routeChangeSuccess",j);j()}}}],Sd=["$templateCache",
+function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],Td=J({terminal:!0}),Ud=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,e={$setViewValue:D};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var h=this,k={},j=e,l;h.databound=d.ngModel;
+h.init=function(a,c,d){j=a;l=d};h.addOption=function(c){k[c]=!0;j.$viewValue==c&&(a.val(c),l.parent()&&l.remove())};h.removeOption=function(a){this.hasOption(a)&&(delete k[a],j.$viewValue==a&&this.renderUnknownOption(a))};h.renderUnknownOption=function(c){c="? "+ja(c)+" ?";l.val(c);a.prepend(l);a.val(c);l.prop("selected",!0)};h.hasOption=function(a){return k.hasOwnProperty(a)};c.$on("$destroy",function(){h.renderUnknownOption=D})}],link:function(e,i,f,h){function k(a,c,d,e){d.$render=function(){var a=
+d.$viewValue;e.hasOption(a)?(z.parent()&&z.remove(),c.val(a),a===""&&v.prop("selected",!0)):t(a)&&v?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){z.parent()&&z.remove();d.$setViewValue(c.val())})})}function j(a,c,d){var e;d.$render=function(){var a=new Fa(d.$viewValue);m(c.children(),function(c){c.selected=u(a.get(c.value))})};a.$watch(function(){ga(e,d.$viewValue)||(e=U(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];m(c.children(),
+function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function l(e,f,g){function h(){var a={"":[]},c=[""],d,i,t,u,s;t=g.$modelValue;u=r(e)||[];var y=l?mb(u):u,z,w,x;w={};s=!1;var C,A;if(o)s=new Fa(t);else if(t===null||q)a[""].push({selected:t===null,id:"",label:""}),s=!0;for(x=0;z=y.length,x<z;x++){w[k]=u[l?w[l]=y[x]:x];d=m(e,w)||"";if(!(i=a[d]))i=a[d]=[],c.push(d);o?d=s.remove(n(e,w))!=p:(d=t===n(e,w),s=s||d);i.push({id:l?y[x]:x,label:j(e,w)||"",selected:d})}!o&&!s&&a[""].unshift({id:"?",
+label:"",selected:!0});w=0;for(y=c.length;w<y;w++){d=c[w];i=a[d];if(v.length<=w)t={element:B.clone().attr("label",d),label:i.label},u=[t],v.push(u),f.append(t.element);else if(u=v[w],t=u[0],t.label!=d)t.element.attr("label",t.label=d);C=null;x=0;for(z=i.length;x<z;x++)if(d=i[x],s=u[x+1]){C=s.element;if(s.label!==d.label)C.text(s.label=d.label);if(s.id!==d.id)C.val(s.id=d.id);if(s.element.selected!==d.selected)C.prop("selected",s.selected=d.selected)}else d.id===""&&q?A=q:(A=D.clone()).val(d.id).attr("selected",
+d.selected).text(d.label),u.push({element:A,label:d.label,id:d.id,selected:d.selected}),C?C.after(A):t.element.append(A),C=A;for(x++;u.length>x;)u.pop().element.remove()}for(;v.length>w;)v.pop()[0].element.remove()}var i;if(!(i=w.match(d)))throw A("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+w+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),r=c(i[7]),v=[[{element:f,label:""}]];q&&(a(q)(e),q.removeClass("ng-scope"),
+q.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=r(e)||[],d={},h,i,j,m,q,s;if(o){i=[];m=0;for(s=v.length;m<s;m++){a=v[m];j=1;for(q=a.length;j<q;j++)if((h=a[j].element)[0].selected)h=h.val(),l&&(d[l]=h),d[k]=c[h],i.push(n(e,d))}}else h=f.val(),h=="?"?i=p:h==""?i=null:(d[k]=c[h],l&&(d[l]=h),i=n(e,d));g.$setViewValue(i)})});g.$render=h;e.$watch(h)}if(h[1]){for(var n=h[0],r=h[1],o=f.multiple,w=f.ngOptions,q=!1,v,D=y(ba.createElement("option")),B=y(ba.createElement("optgroup")),
+z=D.clone(),h=0,x=i.children(),E=x.length;h<E;h++)if(x[h].value==""){v=q=x.eq(h);break}n.init(r,q,z);if(o&&(f.required||f.ngRequired)){var s=function(a){r.$setValidity("required",!f.required||a&&a.length);return a};r.$parsers.push(s);r.$formatters.unshift(s);f.$observe("required",function(){s(r.$viewValue)})}w?l(e,i,r):o?j(e,i,r):k(e,i,r,n)}}}}],Vd=["$interpolate",function(a){var c={addOption:D,removeOption:D};return{restrict:"E",priority:100,compile:function(d,e){if(t(e.value)){var g=a(d.text(),
+!0);g||e.$set("value",d.text())}return function(a,d,e){var k=d.parent(),j=k.data("$selectController")||k.parent().data("$selectController");j&&j.databound?d.prop("selected",!1):j=c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&j.removeOption(c);j.addOption(a)}):j.addOption(e.value);d.bind("$destroy",function(){j.removeOption(e.value)})}}}}],Wd=J({restrict:"E",terminal:!0});(ia=T.jQuery)?(y=ia,x(ia.fn,{scope:ua.scope,controller:ua.controller,injector:ua.injector,inheritedData:ua.inheritedData}),
+bb("remove",!0),bb("empty"),bb("html")):y=P;Zb.element=y;(function(a){x(a,{bootstrap:qb,copy:U,extend:x,equals:ga,element:y,forEach:m,injector:rb,noop:D,bind:Wa,toJson:ca,fromJson:ob,identity:ma,isUndefined:t,isDefined:u,isString:F,isFunction:M,isObject:I,isNumber:wa,isElement:gc,isArray:K,version:id,isDate:na,lowercase:E,uppercase:la,callbacks:{counter:0}});ta=mc(T);try{ta("ngLocale")}catch(c){ta("ngLocale",[]).provider("$locale",Zc)}ta("ng",["ngLocale"],["$provide",function(a){a.provider("$compile",
+Cb).directive({a:jd,input:cc,textarea:cc,form:kd,script:Sd,select:Ud,style:Wd,option:Vd,ngBind:vd,ngBindHtmlUnsafe:xd,ngBindTemplate:wd,ngClass:yd,ngClassEven:Ad,ngClassOdd:zd,ngCsp:Dd,ngCloak:Bd,ngController:Cd,ngForm:ld,ngHide:Ld,ngInclude:Fd,ngInit:Gd,ngNonBindable:Hd,ngPluralize:Id,ngRepeat:Jd,ngShow:Kd,ngSubmit:Ed,ngStyle:Md,ngSwitch:Nd,ngSwitchWhen:Od,ngSwitchDefault:Pd,ngOptions:Td,ngView:Rd,ngTransclude:Qd,ngModel:qd,ngList:sd,ngChange:rd,required:dc,ngRequired:dc,ngValue:ud}).directive(lb).directive(ec);
+a.provider({$anchorScroll:vc,$browser:xc,$cacheFactory:yc,$controller:Bc,$document:Cc,$exceptionHandler:Dc,$filter:Qb,$interpolate:Ec,$http:Vc,$httpBackend:Wc,$location:Ic,$log:Jc,$parse:Nc,$route:Qc,$routeParams:Rc,$rootScope:Sc,$q:Oc,$sniffer:Tc,$templateCache:zc,$timeout:$c,$window:Uc})}])})(Zb);y(ba).ready(function(){kc(ba,qb)})})(window,document);angular.element(document).find("head").append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');

Diff do ficheiro suprimidas por serem muito extensas
+ 5 - 0
common/lib/bootstrap.min.js


Diff do ficheiro suprimidas por serem muito extensas
+ 7 - 0
common/lib/date.js


+ 228 - 0
common/lib/dateformat.js

@@ -0,0 +1,228 @@
+/**
+* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
+* © 2011 Colin Snover <http://zetafleet.com>
+* Released under MIT license.
+*/
+(function (Date, undefined) {
+    var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
+    Date.parse = function (date) {
+        var timestamp, struct, minutesOffset = 0;
+
+        // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
+        // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
+        // implementations could be faster
+        // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
+        if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3-6}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
+            // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
+            for (var i = 0, k; (k = numericKeys[i]); ++i) {
+                struct[k] = +struct[k] || 0;
+            }
+
+            // allow undefined days and months
+            struct[2] = (+struct[2] || 1) - 1;
+            struct[3] = +struct[3] || 1;
+
+            if (struct[8] !== 'Z' && struct[9] !== undefined) {
+                minutesOffset = struct[10] * 60 + struct[11];
+
+                if (struct[9] === '+') {
+                    minutesOffset = 0 - minutesOffset;
+                }
+            }
+
+            timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
+        }
+        else {
+            timestamp = origParse ? origParse(date) : NaN;
+        }
+
+        return timestamp;
+    };
+}(Date));
+
+/**
+* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
+* © 2011 Colin Snover <http://zetafleet.com>
+* Released under MIT license.
+*/
+(function (Date, undefined) {
+    var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
+    Date.parse = function (date) {
+        var timestamp, struct, minutesOffset = 0;
+
+        // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
+        // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
+        // implementations could be faster
+        // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
+        if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,6}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
+            // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
+            for (var i = 0, k; (k = numericKeys[i]); ++i) {
+                struct[k] = +struct[k] || 0;
+            }
+
+            // allow undefined days and months
+            struct[2] = (+struct[2] || 1) - 1;
+            struct[3] = +struct[3] || 1;
+
+            if (struct[8] !== 'Z' && struct[9] !== undefined) {
+                minutesOffset = struct[10] * 60 + struct[11];
+
+                if (struct[9] === '+') {
+                    minutesOffset = 0 - minutesOffset;
+                }
+            }
+            if(struct[7] > 999)
+                struct[7] = parseInt(struct[7].toString().substring(0,2))
+
+            timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
+        }
+        else {
+            timestamp = origParse ? origParse(date) : NaN;
+        }
+
+        return timestamp;
+    };
+}(Date));
+
+/*
+ * Date Format 1.2.3
+ * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
+ * MIT license
+ *
+ * Includes enhancements by Scott Trenda <scott.trenda.net>
+ * and Kris Kowal <cixar.com/~kris.kowal/>
+ *
+ * Accepts a date, a mask, or a date and a mask.
+ * Returns a formatted version of the given date.
+ * The date defaults to the current date/time.
+ * The mask defaults to dateFormat.masks.default.
+ */
+
+var dateFormat = function () {
+    var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
+        timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
+        timezoneClip = /[^-+\dA-Z]/g,
+        pad = function (val, len) {
+            val = String(val);
+            len = len || 2;
+            while (val.length < len) val = "0" + val;
+            return val;
+        };
+
+    // Regexes and supporting functions are cached through closure
+    return function (date, mask, utc) {
+        var dF = dateFormat;
+
+        // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
+        if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
+            mask = date;
+            date = undefined;
+        }
+
+        // Passing date through Date applies Date.parse, if necessary
+        date = date ? new Date(date) : new Date;
+        if (isNaN(date)) throw SyntaxError("invalid date");
+
+        mask = String(dF.masks[mask] || mask || dF.masks["default"]);
+
+        // Allow setting the utc argument via the mask
+        if (mask.slice(0, 4) == "UTC:") {
+            mask = mask.slice(4);
+            utc = true;
+        }
+
+        var _ = utc ? "getUTC" : "get",
+            d = date[_ + "Date"](),
+            D = date[_ + "Day"](),
+            m = date[_ + "Month"](),
+            y = date[_ + "FullYear"](),
+            H = date[_ + "Hours"](),
+            M = date[_ + "Minutes"](),
+            s = date[_ + "Seconds"](),
+            L = date[_ + "Milliseconds"](),
+            o = utc ? 0 : date.getTimezoneOffset(),
+            flags = {
+                d:    d,
+                dd:   pad(d),
+                ddd:  dF.i18n.dayNames[D],
+                dddd: dF.i18n.dayNames[D + 7],
+                m:    m + 1,
+                mm:   pad(m + 1),
+                mmm:  dF.i18n.monthNames[m],
+                mmmm: dF.i18n.monthNames[m + 12],
+                yy:   String(y).slice(2),
+                yyyy: y,
+                h:    H % 12 || 12,
+                hh:   pad(H % 12 || 12),
+                H:    H,
+                HH:   pad(H),
+                M:    M,
+                MM:   pad(M),
+                s:    s,
+                ss:   pad(s),
+                l:    pad(L, 3),
+                L:    pad(L > 99 ? Math.round(L / 10) : L),
+                t:    H < 12 ? "a"  : "p",
+                tt:   H < 12 ? "am" : "pm",
+                T:    H < 12 ? "A"  : "P",
+                TT:   H < 12 ? "AM" : "PM",
+                Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
+                o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
+                S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
+            };
+
+        return mask.replace(token, function ($0) {
+            return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
+        });
+    };
+}();
+
+// Some common format strings
+dateFormat.masks = {
+    "default":      "ddd mmm dd yyyy HH:MM:ss",
+    shortDate:      "m/d/yy",
+    mediumDate:     "mmm d, yyyy",
+    longDate:       "mmmm d, yyyy",
+    fullDate:       "dddd, mmmm d, yyyy",
+    shortTime:      "h:MM TT",
+    mediumTime:     "h:MM:ss TT",
+    longTime:       "h:MM:ss TT Z",
+    isoDate:        "yyyy-mm-dd",
+    isoTime:        "HH:MM:ss",
+    isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
+    isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
+};
+
+// Internationalization strings
+dateFormat.i18n = {
+    dayNames: [
+        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+    ],
+    monthNames: [
+        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+        "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+    ]
+};
+
+// For convenience...
+Date.prototype.format = function (mask, utc) {
+    return dateFormat(this, mask, utc);
+};
+
+
+Date.prototype.add_days = function(days) {
+    var dat = new Date(this.valueOf())
+    dat.setDate(dat.getDate() + days);
+    return dat;
+}
+
+function date_range(startDate, stopDate) {
+    var dateArray = new Array();
+    var currentDate = startDate;
+    while (currentDate <= stopDate) {
+        dateArray.push( new Date (currentDate) )
+        currentDate = currentDate.add_days(1);
+    }
+    return dateArray;
+}

+ 369 - 0
common/lib/datepicker.js

@@ -0,0 +1,369 @@
+/* =========================================================
+ * bootstrap-datepicker.js
+ * original by Stefan Petre
+ * tweaked by gus
+ * ========================================================= */
+
+!function( $ ) {
+
+	// Picker object
+
+	var Datepicker = function(element, options){
+		this.element = $(element);
+
+		this.days = options.days||["sun","mon","tue","wed","thu","fri","sat"];
+		this.months = options.months||["january","february","march","april","may","june","july","august","september","october","november","december"];
+		this.format = options.format||$(element).data("datepicker-format")||'mm/dd/yyyy hh:ii:ss';
+		this.noDefault = options.noDefault||$(element).data("datepicker-nodefault")||false;
+
+		this.picker = $(DPGlobal.template).appendTo("body").on({
+			mousedown: $.proxy(this.click, this)
+		});
+
+		this.weekStart = options.weekStart||0;
+		this.weekEnd = this.weekStart == 0 ? 6 : this.weekStart - 1;
+		this.head();
+
+		if (!this.element.prop("value")&&!this.noDefault) {
+			this.element.prop("value",DPGlobal.formatDate(new Date(), this.format));
+		}
+
+		this.update();
+
+		this.element.on({
+			focus: $.proxy(this.show, this),
+			click: $.proxy(this.show, this),
+			keyup: $.proxy(this.keyup, this)
+		});
+	};
+
+	Datepicker.prototype = {
+		constructor: Datepicker,
+
+		show: function(e) {
+			this.update();
+			this.picker.show();
+			this.height = this.element.outerHeight();
+			this.place();
+			$(window).on("resize", $.proxy(this.place, this));
+			if (e) {
+				e.stopPropagation();
+				e.preventDefault();
+			}
+			this.element.trigger({
+				type: "show",
+				date: this.date
+			});
+			$("body").on("click.bs-sc-datepicker", $.proxy(this.hide, this));
+		},
+
+		hide: function(e){
+			if (e && $(e.target).parents(".bs-sc-datepicker").length) return false;
+			this.picker.hide();
+			$(window).off("resize", this.place);
+			$("body").off("click.bs-sc-datepicker");
+		},
+
+		setValue: function(val) {
+			if (typeof(val)!=='undefined') {
+				this.date = val;
+			}
+			var formated = DPGlobal.formatDate(this.date, this.format);
+			this.element.prop("value", formated);
+		},
+
+		place: function(){
+			var offset = this.element.offset();
+			this.picker.css({
+				top: offset.top + this.height,
+				left: offset.left
+			});
+		},
+
+		update: function(){
+			this.date = DPGlobal.parseDate(this.element.prop("value"), this.format);
+			this.viewDate = new Date(this.date);
+			this.fill();
+		},
+
+		keyup: function() {
+			this.date = DPGlobal.parseDate(this.element.prop("value"), this.format);
+			this.element.trigger({
+				type: 'changeDate',
+				date: this.date
+			});
+		},
+
+		head: function(){
+			var dowCnt = this.weekStart;
+			var html = '<tr>';
+			while (dowCnt < this.weekStart + 7) {
+				html += '<th class="dow">'+this.days[(dowCnt++)%7]+'</th>';
+			}
+			html += '</tr>';
+			this.picker.find(".datepicker-days thead").append(html);
+		},
+
+		fill: function() {
+			var d = new Date(this.viewDate),
+				year = d.getFullYear(),
+				month = d.getMonth(),
+				day = d.getDay();
+
+			currentDate = new Date(this.date.getFullYear(), this.date.getMonth(), this.date.getDate(), 0, 0, 0, 0);
+			currentDate = currentDate.valueOf();
+
+			if (month > 0) {
+				var prevMonth = new Date(year, month-1, 1,0,0,0,0);
+				var prevMonthNr = prevMonth.getMonth();
+			} else {
+				var prevMonth = new Date(year-1, 11, 1, 0, 0, 0, 0);
+				var prevMonthNr = prevMonth.getMonth();
+			}
+
+			if (month < 11) {
+				var nextMonthNr = month + 1;
+			} else {
+				var nextMonthNr = 0;
+			}
+
+			var beginMonth = new Date(year, month, 1,0,0,0,0);
+			startAtWeekday = beginMonth.getDay() - this.weekStart;
+			if (startAtWeekday < 0) {
+				prevMonthDays = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
+				startPrevMonthAtDate = prevMonthDays - (6 + startAtWeekday);
+			} else if (startAtWeekday > 0) {
+				prevMonthDays = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
+				startPrevMonthAtDate = prevMonthDays - startAtWeekday + 1;
+			} else {
+				startPrevMonthAtDate = 1;
+				prevMonth.setMonth(month);
+			}
+
+			prevMonth.setDate(startPrevMonthAtDate);
+
+			d = prevMonth;
+
+			html = []; allDone = false; x=0;
+
+			while(!allDone) {
+
+				if (d.getDay() == this.weekStart) {
+
+					html.push('<tr>');
+				}
+
+				clsName = '';
+				if (d.getMonth() == prevMonthNr) {
+					clsName += ' old';
+				} else if (d.getMonth() == nextMonthNr) {
+					clsName += ' new';
+				}
+				if (d.valueOf() == currentDate) {
+					clsName += ' active';
+				}
+				clsName += ' ' + d.valueOf();
+				html.push('<td class="day'+clsName+'">' + d.getDate() + '</td>');
+
+				if (d.getDay() == this.weekEnd) {
+					html.push('</tr>');
+				}
+
+				d.setDate(d.getDate()+1);
+				allDone = ((d.getDay() == this.weekStart) && (d.getMonth() == nextMonthNr));
+
+				x++;
+				if (x > 99) {
+					console.log("safety");
+					return;
+				}
+			}
+
+			this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
+
+			headerStr = this.months[this.viewDate.getMonth()] + ' ' + this.viewDate.getFullYear();
+			this.picker.find('.datepicker-days thead .monthname').html(headerStr);
+		},
+
+		click: function(e) {
+			e.stopPropagation();
+			e.preventDefault();
+			var target = $(e.target).closest('span, td, th');
+			if (target.length == 1) {
+
+				switch(target[0].nodeName.toLowerCase()) {
+
+					case 'th':
+						switch(target[0].className) {
+							case 'prev':
+								if (this.viewDate.getMonth() > 0) {
+									this.viewDate.setMonth(this.viewDate.getMonth() - 1);
+								} else {
+									this.viewDate.setFullYear(this.viewDate.getFullYear() - 1);
+									this.viewDate.setMonth(11);
+								}
+								break;
+
+							case 'next':
+								if (this.viewDate.getMonth() < 11) {
+									this.viewDate.setMonth(this.viewDate.getMonth() + 1);
+								} else {
+									this.viewDate.setFullYear(this.viewDate.getFullYear() + 1);
+									this.viewDate.setMonth(0);
+								}
+
+								break;
+						}
+						this.fill();
+						break;
+					case 'td':
+						if (target.is('.day')){
+							if (target.is('.old')) {
+								return;
+							} else if (target.is('.new')) {
+								return;
+							}
+
+							var day = parseInt(target.text(), 10)||1;
+							var month = this.viewDate.getMonth();
+							var year = this.viewDate.getFullYear();
+							this.date = new Date(year, month, day,this.date.getHours(),this.date.getMinutes(),this.date.getSeconds(),0);
+							this.viewDate = new Date(year, month, day,0,0,0,0);
+							this.fill();
+							this.setValue();
+							this.element.trigger({
+								type: 'changeDate',
+								date: this.date
+							});
+							this.hide();
+						}
+						break;
+				}
+			}
+			return false;
+		},
+
+	};
+
+	$.fn.datepicker = function ( option ) {
+		return this.each(function () {
+			var $this = $(this),
+				data = $this.data('datepicker'),
+				options = typeof option == 'object' && option;
+			if (!data) {
+				$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
+			}
+			if (typeof option == 'string') data[option]();
+		});
+	};
+
+	$.fn.datepicker.defaults = {
+	};
+	$.fn.datepicker.Constructor = Datepicker;
+
+	var DPGlobal = {
+		isLeapYear: function (year) {
+			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
+		},
+		getDaysInMonth: function (year, month) {
+			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
+		},
+		parseDate: function(dateStr, format) { //convert str into date
+			dateStr = dateStr.replace(/:/g, '/');
+			dateStr = dateStr.replace(/ /g, '/');
+			strParts = dateStr.split('/');
+
+			format = format.replace(/:/g, '/');
+			format = format.replace(/ /g, '/');
+			formatParts = format.split('/');
+
+			date = new Date(),
+			date.setHours(0); date.setMinutes(0); date.setSeconds(0); date.setMilliseconds(0);
+
+			for (var key in formatParts) {
+				if (typeof strParts[key] != 'undefined') {
+					val = strParts[key];
+					switch(formatParts[key]) {
+						case 'dd':
+						case 'd':
+							date.setDate(val);
+							break;
+						case 'mm':
+						case 'm':
+							date.setMonth(val - 1);
+							break;
+						case 'yy':
+							date.setFullYear(2000 + val);
+							break;
+						case 'yyyy':
+							date.setFullYear(val);
+							break;
+						case 'hh':
+						case 'h':
+							date.setHours(val);
+							break;
+						case 'ii':
+						case 'i':
+							date.setMinutes(val);
+							break;
+						case 'ss':
+						case 's':
+							date.setSeconds(val);
+							break;
+					}
+				}
+			}
+			return date;
+		},
+		formatDate: function(date, format){ // build a formatted string
+			var templateParts = {
+				dd: (date.getDate() < 10 ? '0' : '') + date.getDate(),
+				d: date.getDate(),
+				mm: ((date.getMonth() + 1) < 10 ? '0' : '') + (date.getMonth() + 1),
+				m: date.getMonth() + 1,
+				yyyy: date.getFullYear(),
+				yy: date.getFullYear().toString().substring(2),
+				hh: (date.getHours() < 10 ? '0' : '') + date.getHours(),
+				h: date.getHours(),
+				ii: (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(),
+				i: date.getMinutes(),
+				ss: (date.getSeconds() < 10 ? '0' : '') + date.getSeconds(),
+				s: date.getSeconds()
+			};
+
+			var dateStr = format;
+
+			for (var key in templateParts) {
+			    val = templateParts[key];
+			    dateStr = dateStr.replace(key, val);
+			}
+
+			return dateStr;
+		},
+		headTemplate: '<thead>'+
+							'<tr>'+
+								'<th class="prev"><i>&larr;</i></th>'+
+								'<th colspan="5" class="monthname"></th>'+
+								'<th class="next"><i>&rarr;</i></th>'+
+							'</tr>'+
+						'</thead>',
+		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
+	};
+	DPGlobal.template = '<div class="bs-sc-datepicker dropdown-menu">'+
+							'<div class="datepicker-days">'+
+								'<table class=" table-condensed">'+
+									DPGlobal.headTemplate+
+									'<tbody></tbody>'+
+								'</table>'+
+							'</div>'+
+						'</div>';
+
+}( window.jQuery );
+
+$(function() {
+	$("input[data-datepicker-format]").datepicker({
+		weekStart: 1,
+		days: ["zo","ma","di","wo","do","vr","za"],
+		months: ["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"]
+	});
+});

+ 80 - 0
common/lib/elastic-angular-client.js

@@ -0,0 +1,80 @@
+/*! elastic.js - v1.0.0 - 2013-01-15
+* https://github.com/fullscale/elastic.js
+* Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */
+
+/*jshint browser:true */
+/*global angular:true */
+'use strict';
+
+/* 
+Angular.js service wrapping the elastic.js API. This module can simply
+be injected into your angular controllers. 
+*/
+angular.module('elasticjs.service', [])
+  .factory('ejsResource', ['$http', function ($http) {
+
+  return function (url) {
+
+    var
+
+      // use existing ejs object if it exists
+      ejs = window.ejs || {},
+
+      /* results are returned as a promise */
+      promiseThen = function (httpPromise, successcb, errorcb) {
+        return httpPromise.then(function (response) {
+          (successcb || angular.noop)(response.data);
+          return response.data;
+        }, function (response) {
+          (errorcb || angular.noop)(undefined);
+          return undefined;
+        });
+      };
+
+    // set url to empty string if it was not specified
+    if (url == null) {
+      url = '';
+    }
+
+    /* implement the elastic.js client interface for angular */
+    ejs.client = {
+      server: function (s) {
+        if (s == null) {
+          return url;
+        }
+      
+        url = s;
+        return this;
+      },
+      post: function (path, data, successcb, errorcb) {
+        path = url + path;
+        return promiseThen($http.post(path, data), successcb, errorcb);
+      },
+      get: function (path, data, successcb, errorcb) {
+        path = url + path;
+        return promiseThen($http.get(path, data), successcb, errorcb);
+      },
+      put: function (path, data, successcb, errorcb) {
+        path = url + path;
+        return promiseThen($http.put(path, data), successcb, errorcb);
+      },
+      del: function (path, data, successcb, errorcb) {
+        path = url + path;
+        return promiseThen($http.delee(path, data), successcb, errorcb);
+      },
+      head: function (path, data, successcb, errorcb) {
+        path = url + path;
+        return $http.head(path, data)
+          .then(function (response) {
+          (successcb || angular.noop)(response.headers());
+          return response.headers();
+        }, function (response) {
+          (errorcb || angular.noop)(undefined);
+          return undefined;
+        });
+      }
+    };
+  
+    return ejs;
+  };
+}]);

+ 4 - 0
common/lib/elastic-angular-client.min.js

@@ -0,0 +1,4 @@
+/*! elastic.js - v1.0.0 - 2013-01-15
+* https://github.com/fullscale/elastic.js
+* Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */
+"use strict";angular.module("elasticjs.service",[]).factory("ejsResource",["$http",function(e){return function(t){var n=window.ejs||{},r=function(e,t,n){return e.then(function(e){return(t||angular.noop)(e.data),e.data},function(e){return(n||angular.noop)(undefined),undefined})};return t==null&&(t=""),n.client={server:function(e){return e==null?t:(t=e,this)},post:function(n,i,s,o){return n=t+n,r(e.post(n,i),s,o)},get:function(n,i,s,o){return n=t+n,r(e.get(n,i),s,o)},put:function(n,i,s,o){return n=t+n,r(e.put(n,i),s,o)},del:function(n,i,s,o){return n=t+n,r(e.delee(n,i),s,o)},head:function(n,r,i,s){return n=t+n,e.head(n,r).then(function(e){return(i||angular.noop)(e.headers()),e.headers()},function(e){return(s||angular.noop)(undefined),undefined})}},n}}]);

+ 17990 - 0
common/lib/elastic.js

@@ -0,0 +1,17990 @@
+/*! elastic.js - v1.0.0 - 2013-01-15
+* https://github.com/fullscale/elastic.js
+* Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */
+
+/**
+ @namespace
+ @name ejs
+ @desc All elastic.js modules are organized under the ejs namespace.
+ */
+(function () {
+  'use strict';
+
+  var 
+
+    // save reference to global object
+    // `window` in browser
+    // `exports` on server
+    root = this,
+    
+    // save the previous version of ejs
+    _ejs = root && root.ejs,
+
+    // from underscore.js, used in utils
+    ArrayProto = Array.prototype, 
+    ObjProto = Object.prototype, 
+    slice = ArrayProto.slice,
+    toString = ObjProto.toString,
+    hasOwnProp = ObjProto.hasOwnProperty,
+    nativeForEach = ArrayProto.forEach,
+    nativeIsArray = Array.isArray,
+    breaker = {},
+    has,
+    each,
+    extend,
+    isArray,
+    isObject,
+    isString,
+    isNumber,
+    isFunction,
+    isEJSObject, // checks if valid ejs object
+    isQuery, // checks valid ejs Query object
+    isFilter, // checks valid ejs Filter object
+    isFacet, // checks valid ejs Facet object
+    isScriptField, // checks valid ejs ScriptField object
+    isGeoPoint, // checks valid ejs GeoPoint object
+    isIndexedShape, // checks valid ejs IndexedShape object
+    isShape, // checks valid ejs Shape object
+    isSort, // checks valid ejs Sort object
+    isHighlight, // checks valid ejs Highlight object
+    
+    // create ejs object
+    ejs;
+    
+  if (typeof exports !== 'undefined') {
+    ejs = exports;
+  } else {
+    ejs = root.ejs = {};
+  }
+
+  /* Utility methods, most of which are pulled from underscore.js. */
+  
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  has = function (obj, key) {
+    return hasOwnProp.call(obj, key);
+  };
+    
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles objects with the built-in `forEach`, arrays, and raw objects.
+  // Delegates to **ECMAScript 5**'s native `forEach` if available.
+  each = function (obj, iterator, context) {
+    if (obj == null) {
+      return;
+    }
+    if (nativeForEach && obj.forEach === nativeForEach) {
+      obj.forEach(iterator, context);
+    } else if (obj.length === +obj.length) {
+      for (var i = 0, l = obj.length; i < l; i++) {
+        if (iterator.call(context, obj[i], i, obj) === breaker) {
+          return;
+        }
+      }
+    } else {
+      for (var key in obj) {
+        if (has(obj, key)) {
+          if (iterator.call(context, obj[key], key, obj) === breaker) {
+            return;
+          }
+        }
+      }
+    }
+  };
+      
+  // Extend a given object with all the properties in passed-in object(s).
+  extend = function (obj) {
+    each(slice.call(arguments, 1), function (source) {
+      for (var prop in source) {
+        obj[prop] = source[prop];
+      }
+    });
+    return obj;
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  // switched to ===, not sure why underscore used ==
+  isArray = nativeIsArray || function (obj) {
+    return toString.call(obj) === '[object Array]';
+  };
+
+  // Is a given variable an object?
+  isObject = function (obj) {
+    return obj === Object(obj);
+  };
+  
+  // switched to ===, not sure why underscore used ==
+  isString = function (obj) {
+    return toString.call(obj) === '[object String]';
+  };
+  
+  // switched to ===, not sure why underscore used ==
+  isNumber = function (obj) {
+    return toString.call(obj) === '[object Number]';
+  };
+  
+  // switched to ===, not sure why underscore used ==
+  if (typeof (/./) !== 'function') {
+    isFunction = function (obj) {
+      return typeof obj === 'function';
+    };
+  } else {
+    isFunction = function (obj) {
+      return toString.call(obj) === '[object Function]';
+    };
+  }
+  
+  // Is a given value an ejs object?
+  // Yes if object and has "_type", "_self", and "toString" properties
+  isEJSObject = function (obj) {
+    return (isObject(obj) &&
+      has(obj, '_type') &&
+      has(obj, '_self') && 
+      has(obj, 'toString'));
+  };
+  
+  isQuery = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'query');
+  };
+  
+  isFilter = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'filter');
+  };
+  
+  isFacet = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'facet');
+  };
+  
+  isScriptField = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'script field');
+  };
+  
+  isGeoPoint = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'geo point');
+  };
+  
+  isIndexedShape = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'indexed shape');
+  };
+  
+  isShape = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'shape');
+  };
+  
+  isSort = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'sort');
+  };
+  
+  isHighlight = function (obj) {
+    return (isEJSObject(obj) && obj._type() === 'highlight');
+  };
+  
+  /**
+    @class
+    <p>The DateHistogram facet works with time-based values by building a histogram across time
+       intervals of the <code>value</code> field. Each value is <em>rounded</em> into an interval (or
+       placed in a bucket), and statistics are provided per interval/bucket (count and total).</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.DateHistogramFacet
+
+    @desc
+    <p>A facet which returns the N most frequent terms within a collection
+       or set of collections.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.DateHistogramFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.DateHistogramFacet
+        @property {Object} facet
+        */
+    var facet = {};
+
+    facet[name] = {
+      date_histogram: {}
+    };
+
+    return {
+
+      /**
+            Sets the field to be used to construct the this facet.
+
+            @member ejs.DateHistogramFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].date_histogram.field;
+        }
+      
+        facet[name].date_histogram.field = fieldName;
+        return this;
+      },
+
+      /**
+            Allows you to specify a different key field to be used to group intervals.
+
+            @member ejs.DateHistogramFacet
+            @param {String} fieldName The name of the field to be used.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      keyField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].date_histogram.key_field;
+        }
+      
+        facet[name].date_histogram.key_field = fieldName;
+        return this;
+      },
+      
+      /**
+            Allows you to specify a different value field to aggrerate over.
+
+            @member ejs.DateHistogramFacet
+            @param {String} fieldName The name of the field to be used.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].date_histogram.value_field;
+        }
+      
+        facet[name].date_histogram.value_field = fieldName;
+        return this;
+      },
+      
+      /**
+            Sets the bucket interval used to calculate the distribution.
+
+            @member ejs.DateHistogramFacet
+            @param {String} timeInterval The bucket interval. Valid values are <code>year, month, week, day, hour,</code> and <code>minute</code>.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      interval: function (timeInterval) {
+        if (timeInterval == null) {
+          return facet[name].date_histogram.interval;
+        }
+      
+        facet[name].date_histogram.interval = timeInterval;
+        return this;
+      },
+
+      /**
+            By default, time values are stored in UTC format. This method allows users
+            to set a time zone value that is then used to compute intervals 
+            before rounding on the interval value.  Equalivent to 
+            <coe>preZone</code>.  Use <code>preZone</code> if possible.  The 
+            value is an offset from UTC.
+            
+            For example, to use EST you would set the value to <code>-5</code>.
+
+            @member ejs.DateHistogramFacet
+            @param {Integer} tz An offset value from UTC.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      timeZone: function (tz) {
+        if (tz == null) {
+          return facet[name].date_histogram.time_zone;
+        }
+      
+        facet[name].date_histogram.time_zone = tz;
+        return this;
+      },
+
+      /**
+            By default, time values are stored in UTC format. This method allows users
+            to set a time zone value that is then used to compute intervals 
+            before rounding on the interval value.  The value is an offset
+            from UTC.
+            
+            For example, to use EST you would set the value to <code>-5</code>.
+
+            @member ejs.DateHistogramFacet
+            @param {Integer} tz An offset value from UTC.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      preZone: function (tz) {
+        if (tz == null) {
+          return facet[name].date_histogram.pre_zone;
+        }
+      
+        facet[name].date_histogram.pre_zone = tz;
+        return this;
+      },
+      
+      /**
+            Enables large date interval conversions (day and up).  Set to 
+            true to enable and then set the <code>interval</code> to an 
+            interval greater than a day.
+            
+            @member ejs.DateHistogramFacet
+            @param {Boolean} trueFalse A valid boolean value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      preZoneAdjustLargeInterval: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].date_histogram.pre_zone_adjust_large_interval;
+        }
+      
+        facet[name].date_histogram.pre_zone_adjust_large_interval = trueFalse;
+        return this;
+      },
+      
+      /**
+            By default, time values are stored in UTC format. This method allows users
+            to set a time zone value that is then used to compute intervals 
+            after rounding on the interval value.  The value is an offset
+            from UTC.  The tz offset value is simply added to the resulting 
+            bucket's date value.
+            
+            For example, to use EST you would set the value to <code>-5</code>.
+
+            @member ejs.DateHistogramFacet
+            @param {Integer} tz An offset value from UTC.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      postZone: function (tz) {
+        if (tz == null) {
+          return facet[name].date_histogram.post_zone;
+        }
+      
+        facet[name].date_histogram.post_zone = tz;
+        return this;
+      },
+
+      /**
+            Set's a specific pre-rounding offset.  Format is 1d, 1h, etc.
+
+            @member ejs.DateHistogramFacet
+            @param {String} offset The offset as a string (1d, 1h, etc)
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      preOffset: function (offset) {
+        if (offset == null) {
+          return facet[name].date_histogram.pre_offset;
+        }
+      
+        facet[name].date_histogram.pre_offset = offset;
+        return this;
+      },
+      
+      /**
+            Set's a specific post-rounding offset.  Format is 1d, 1h, etc.
+
+            @member ejs.DateHistogramFacet
+            @param {String} offset The offset as a string (1d, 1h, etc)
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      postOffset: function (offset) {
+        if (offset == null) {
+          return facet[name].date_histogram.post_offset;
+        }
+      
+        facet[name].date_histogram.post_offset = offset;
+        return this;
+      },
+      
+      /**
+            The date histogram works on numeric values (since time is stored 
+            in milliseconds since the epoch in UTC). But, sometimes, systems 
+            will store a different resolution (like seconds since UTC) in a 
+            numeric field. The factor parameter can be used to change the 
+            value in the field to milliseconds to actual do the relevant 
+            rounding, and then be applied again to get to the original unit. 
+            For example, when storing in a numeric field seconds resolution, 
+            the factor can be set to 1000.
+
+            @member ejs.DateHistogramFacet
+            @param {Integer} f The conversion factor.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      factor: function (f) {
+        if (f == null) {
+          return facet[name].date_histogram.factor;
+        }
+      
+        facet[name].date_histogram.factor = f;
+        return this;
+      },
+      
+      /**
+            Allows you modify the <code>value</code> field using a script. The modified value
+            is then used to compute the statistical data.
+
+            @member ejs.DateHistogramFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueScript: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].date_histogram.value_script;
+        }
+      
+        facet[name].date_histogram.value_script = scriptCode;
+        return this;
+      },
+
+      /**
+            Sets the type of ordering that will be performed on the date
+            buckets.  Valid values are:
+            
+            time - the default, sort by the buckets start time in milliseconds.
+            count - sort by the number of items in the bucket
+            total - sort by the sum/total of the items in the bucket
+            
+            @member ejs.DateHistogramFacet
+            @param {String} o The ordering method: time, count, or total.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      order: function (o) {
+        if (o == null) {
+          return facet[name].date_histogram.order;
+        }
+      
+        o = o.toLowerCase();
+        if (o === 'time' || o === 'count' || o === 'total') {
+          facet[name].date_histogram.order = o;
+        }
+        
+        return this;
+      },
+      
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.DateHistogramFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].date_histogram.lang;
+        }
+      
+        facet[name].date_histogram.lang = language;
+        return this;
+      },
+
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.DateHistogramFacet
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return facet[name].date_histogram.params;
+        }
+    
+        facet[name].date_histogram.params = p;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.DateHistogramFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.DateHistogramFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.DateHistogramFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.DateHistogramFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.DateHistogramFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.DateHistogramFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.DateHistogramFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.DateHistogramFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The FilterFacet allows you to specify any valid <code>Filter</code> and
+    have the number of matching hits returned as the value.</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.FilterFacet
+
+    @desc
+    <p>A facet that return a count of the hits matching the given filter.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.FilterFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.FilterFacet
+        @property {Object} facet
+        */
+    var facet = {};
+    facet[name] = {};
+
+    return {
+
+      /**
+            <p>Sets the filter to be used for this facet.</p>
+
+            @member ejs.FilterFacet
+            @param {Object} oFilter A valid <code>Query</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      filter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.FilterFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.FilterFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.FilterFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.FilterFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.FilterFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.FilterFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FilterFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.FilterFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The geoDistanceFacet facet provides information over a range of distances from a
+    provided point. This includes the number of hits that fall within each range,
+    along with aggregate information (like total).</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.GeoDistanceFacet
+
+    @desc
+    <p>A facet which provides information over a range of distances from a provided point.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.GeoDistanceFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.GeoDistanceFacet
+        @property {Object} facet
+        */
+    var facet = {},
+        point = ejs.GeoPoint([0, 0]),
+        field = 'location';
+
+    facet[name] = {
+      geo_distance: {
+        location: point._self(),
+        ranges: []
+      }
+    };
+
+    return {
+
+      /**
+            Sets the document field containing the geo-coordinate to be used 
+            to calculate the distance.  Defaults to "location".
+
+            @member ejs.GeoDistanceFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (fieldName) {
+        var oldValue = facet[name].geo_distance[field];
+        
+        if (fieldName == null) {
+          return field;
+        }
+
+        delete facet[name].geo_distance[field];
+        field = fieldName;
+        facet[name].geo_distance[fieldName] = oldValue;
+        
+        return this;
+      },
+
+      /**
+            Sets the point of origin from where distances will be measured.
+
+            @member ejs.GeoDistanceFacet
+            @param {GeoPoint} p A valid GeoPoint object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      point: function (p) {
+        if (p == null) {
+          return point;
+        }
+      
+        if (!isGeoPoint(p)) {
+          throw new TypeError('Argument must be a GeoPoint');
+        }
+        
+        point = p;
+        facet[name].geo_distance[field] = p._self();
+        return this;
+      },
+
+      /**
+            Adds a new bounded range.
+
+            @member ejs.GeoDistanceFacet
+            @param {Number} from The lower bound of the range
+            @param {Number} to The upper bound of the range
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      addRange: function (from, to) {
+        if (arguments.length === 0) {
+          return facet[name].geo_distance.ranges;
+        }
+      
+        facet[name].geo_distance.ranges.push({
+          from: from,
+          to: to
+        });
+        
+        return this;
+      },
+
+      /**
+            Adds a new unbounded lower limit.
+
+            @member ejs.GeoDistanceFacet
+            @param {Number} from The lower limit of the unbounded range
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      addUnboundedFrom: function (from) {
+        if (from == null) {
+          return facet[name].geo_distance.ranges;
+        }
+      
+        facet[name].geo_distance.ranges.push({
+          from: from
+        });
+        
+        return this;
+      },
+
+      /**
+            Adds a new unbounded upper limit.
+
+            @member ejs.GeoDistanceFacet
+            @param {Number} to The upper limit of the unbounded range
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      addUnboundedTo: function (to) {
+        if (to == null) {
+          return facet[name].geo_distance.ranges;
+        }
+      
+        facet[name].geo_distance.ranges.push({
+          to: to
+        });
+        
+        return this;
+      },
+
+      /**
+             Sets the distance unit.  Valid values are "mi" for miles or "km"
+             for kilometers. Defaults to "km".
+
+             @member ejs.GeoDistanceFacet
+             @param {Number} unit the unit of distance measure.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      unit: function (unit) {
+        if (unit == null) {
+          return facet[name].geo_distance.unit;
+        }
+      
+        unit = unit.toLowerCase();
+        if (unit === 'mi' || unit === 'km') {
+          facet[name].geo_distance.unit = unit;
+        }
+        
+        return this;
+      },
+      
+      /**
+            How to compute the distance. Can either be arc (better precision) 
+            or plane (faster). Defaults to arc.
+
+            @member ejs.GeoDistanceFacet
+            @param {String} type The execution type as a string.  
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      distanceType: function (type) {
+        if (type == null) {
+          return facet[name].geo_distance.distance_type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'arc' || type === 'plane') {
+          facet[name].geo_distance.distance_type = type;
+        }
+        
+        return this;
+      },
+
+      /**
+            If the lat/long points should be normalized to lie within their
+            respective normalized ranges.
+            
+            Normalized ranges are:
+            lon = -180 (exclusive) to 180 (inclusive) range
+            lat = -90 to 90 (both inclusive) range
+
+            @member ejs.GeoDistanceFacet
+            @param {String} trueFalse True if the coordinates should be normalized. False otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      normalize: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].geo_distance.normalize;
+        }
+
+        facet[name].geo_distance.normalize = trueFalse;
+        return this;
+      },
+      
+      /**
+            Allows you to specify a different value field to aggrerate over.
+
+            @member ejs.GeoDistanceFacet
+            @param {String} fieldName The name of the field to be used.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].geo_distance.value_field;
+        }
+      
+        facet[name].geo_distance.value_field = fieldName;
+        return this;
+      },
+      
+      /**
+            Allows you modify the <code>value</code> field using a script. The modified value
+            is then used to compute the statistical data.
+
+            @member ejs.GeoDistanceFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueScript: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].geo_distance.value_script;
+        }
+      
+        facet[name].geo_distance.value_script = scriptCode;
+        return this;
+      },
+      
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.GeoDistanceFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].geo_distance.lang;
+        }
+      
+        facet[name].geo_distance.lang = language;
+        return this;
+      },
+      
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.GeoDistanceFacet
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return facet[name].geo_distance.params;
+        }
+    
+        facet[name].geo_distance.params = p;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.GeoDistanceFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.GeoDistanceFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.GeoDistanceFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.GeoDistanceFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.GeoDistanceFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.GeoDistanceFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoDistanceFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.GeoDistanceFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The histogram facet works with numeric data by building a histogram across intervals
+       of the field values. Each value is <em>rounded</em> into an interval (or placed in a
+       bucket), and statistics are provided per interval/bucket (count and total).</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.HistogramFacet
+
+    @desc
+    <p>A facet which returns the N most frequent terms within a collection
+       or set of collections.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.HistogramFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.HistogramFacet
+        @property {Object} facet
+        */
+    var facet = {};
+
+    facet[name] = {
+      histogram: {}
+    };
+
+    return {
+
+      /**
+            Sets the field to be used to construct the this facet.
+
+            @member ejs.HistogramFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].histogram.field;
+        }
+      
+        facet[name].histogram.field = fieldName;
+        return this;
+      },
+
+      /**
+            Sets the bucket interval used to calculate the distribution.
+
+            @member ejs.HistogramFacet
+            @param {Number} numericInterval The bucket interval in which to group values.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      interval: function (numericInterval) {
+        if (numericInterval == null) {
+          return facet[name].histogram.interval;
+        }
+      
+        facet[name].histogram.interval = numericInterval;
+        return this;
+      },
+
+      /**
+            Sets the bucket interval used to calculate the distribution based
+            on a time value such as "1d", "1w", etc.
+
+            @member ejs.HistogramFacet
+            @param {Number} timeInterval The bucket interval in which to group values.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      timeInterval: function (timeInterval) {
+        if (timeInterval == null) {
+          return facet[name].histogram.time_interval;
+        }
+      
+        facet[name].histogram.time_interval = timeInterval;
+        return this;
+      },
+
+      /**
+            Sets the "from", "start", or lower bounds bucket.  For example if 
+            you have a value of 1023, an interval of 100, and a from value of 
+            1500, it will be placed into the 1500 bucket vs. the normal bucket 
+            of 1000.
+
+            @member ejs.HistogramFacet
+            @param {Number} from the lower bounds bucket value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      from: function (from) {
+        if (from == null) {
+          return facet[name].histogram.from;
+        }
+      
+        facet[name].histogram.from = from;
+        return this;
+      },
+
+      /**
+            Sets the "to", "end", or upper bounds bucket.  For example if 
+            you have a value of 1023, an interval of 100, and a to value of 
+            900, it will be placed into the 900 bucket vs. the normal bucket 
+            of 1000.
+
+            @member ejs.HistogramFacet
+            @param {Number} to the upper bounds bucket value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      to: function (to) {
+        if (to == null) {
+          return facet[name].histogram.to;
+        }
+      
+        facet[name].histogram.to = to;
+        return this;
+      },
+                  
+      /**
+            Allows you to specify a different value field to aggrerate over.
+
+            @member ejs.HistogramFacet
+            @param {String} fieldName The name of the field to be used.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].histogram.value_field;
+        }
+      
+        facet[name].histogram.value_field = fieldName;
+        return this;
+      },
+
+      /**
+            Allows you to specify a different key field to be used to group intervals.
+
+            @member ejs.HistogramFacet
+            @param {String} fieldName The name of the field to be used.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      keyField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].histogram.key_field;
+        }
+      
+        facet[name].histogram.key_field = fieldName;
+        return this;
+      },
+
+      /**
+            Allows you modify the <code>value</code> field using a script. The modified value
+            is then used to compute the statistical data.
+
+            @member ejs.HistogramFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueScript: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].histogram.value_script;
+        }
+      
+        facet[name].histogram.value_script = scriptCode;
+        return this;
+      },
+
+      /**
+            Allows you modify the <code>key</code> field using a script. The modified value
+            is then used to generate the interval.
+
+            @member ejs.HistogramFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      keyScript: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].histogram.key_script;
+        }
+      
+        facet[name].histogram.key_script = scriptCode;
+        return this;
+      },
+
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.HistogramFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].histogram.lang;
+        }
+      
+        facet[name].histogram.lang = language;
+        return this;
+      },
+
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.HistogramFacet
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return facet[name].histogram.params;
+        }
+    
+        facet[name].histogram.params = p;
+        return this;
+      },
+      
+      /**
+            Sets the type of ordering that will be performed on the date
+            buckets.  Valid values are:
+            
+            key - the default, sort by the bucket's key value
+            count - sort by the number of items in the bucket
+            total - sort by the sum/total of the items in the bucket
+            
+            @member ejs.HistogramFacet
+            @param {String} o The ordering method: key, count, or total.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      order: function (o) {
+        if (o == null) {
+          return facet[name].histogram.order;
+        }
+      
+        o = o.toLowerCase();
+        if (o === 'key' || o === 'count' || o === 'total') {
+          facet[name].histogram.order = o;
+        }
+        
+        return this;
+      },
+      
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.HistogramFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.HistogramFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.HistogramFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.HistogramFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.HistogramFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.HistogramFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.HistogramFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.HistogramFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The QueryFacet facet allows you to specify any valid <code>Query</code> and
+    have the number of matching hits returned as the value.</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.QueryFacet
+
+    @desc
+    <p>A facet that return a count of the hits matching the given query.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.QueryFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.QueryFacet
+        @property {Object} facet
+        */
+    var facet = {};
+    facet[name] = {};
+
+    return {
+
+      /**
+            <p>Sets the query to be used for this facet.</p>
+
+            @member ejs.QueryFacet
+            @param {Object} oQuery A valid <code>Query</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (oQuery) {
+        if (oQuery == null) {
+          return facet[name].query;
+        }
+      
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        facet[name].query = oQuery._self();
+        return this;
+      },
+
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.QueryFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argumnet must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.QueryFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.QueryFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.QueryFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.QueryFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.QueryFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.QueryFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.QueryFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A RangeFacet allows you to specify a set of ranges and get both the number of docs (count) that
+       fall within each range, and aggregated data based on the field, or another specified field.</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.RangeFacet
+
+    @desc
+    <p>A facet which provides information over a range of numeric intervals.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.RangeFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.RangeFacet
+        @property {Object} facet
+        */
+    var facet = {};
+
+    facet[name] = {
+      range: {
+        ranges: []
+      }
+    };
+
+    return {
+
+      /**
+            Sets the document field to be used for the facet.
+
+            @member ejs.RangeFacet
+            @param {String} fieldName The field name whose data will be used to compute the interval.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].range.field;
+        }
+      
+        facet[name].range.field = fieldName;
+        return this;
+      },
+
+      /**
+            Allows you to specify an alternate key field to be used to compute the interval.
+
+            @member ejs.RangeFacet
+            @param {String} fieldName The field name whose data will be used to compute the interval.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      keyField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].range.key_field;
+        }
+      
+        facet[name].range.key_field = fieldName;
+        return this;
+      },
+
+      /**
+            Allows you to specify an alternate value field to be used to compute statistical information.
+
+            @member ejs.RangeFacet
+            @param {String} fieldName The field name whose data will be used to compute statistics.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].range.value_field;
+        }
+      
+        facet[name].range.value_field = fieldName;
+        return this;
+      },
+
+      /**
+            Allows you modify the <code>value</code> field using a script. The modified value
+            is then used to compute the statistical data.
+
+            @member ejs.RangeFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueScript: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].range.value_script;
+        }
+      
+        facet[name].range.value_script = scriptCode;
+        return this;
+      },
+
+      /**
+            Allows you modify the <code>key</code> field using a script. The modified value
+            is then used to generate the interval.
+
+            @member ejs.RangeFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      keyScript: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].range.key_script;
+        }
+      
+        facet[name].range.key_script = scriptCode;
+        return this;
+      },
+
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.RangeFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].range.lang;
+        }
+      
+        facet[name].range.lang = language;
+        return this;
+      },
+
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.RangeFacet
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return facet[name].range.params;
+        }
+    
+        facet[name].range.params = p;
+        return this;
+      },
+      
+      /**
+            Adds a new bounded range.
+
+            @member ejs.RangeFacet
+            @param {Number} from The lower bound of the range (can also be <code>Date</code>).
+            @param {Number} to The upper bound of the range (can also be <code>Date</code>).
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      addRange: function (from, to) {
+        if (arguments.length === 0) {
+          return facet[name].range.ranges;
+        }
+      
+        facet[name].range.ranges.push({
+          from: from,
+          to: to
+        });
+        
+        return this;
+      },
+
+      /**
+            Adds a new unbounded lower limit.
+
+            @member ejs.RangeFacet
+            @param {Number} from The lower limit of the unbounded range (can also be <code>Date</code>).
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      addUnboundedFrom: function (from) {
+        if (from == null) {
+          return facet[name].range.ranges;
+        }
+      
+        facet[name].range.ranges.push({
+          from: from
+        });
+        
+        return this;
+      },
+
+      /**
+            Adds a new unbounded upper limit.
+
+            @member ejs.RangeFacet
+            @param {Number} to The upper limit of the unbounded range (can also be <code>Date</code>).
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      addUnboundedTo: function (to) {
+        if (to == null) {
+          return facet[name].range.ranges;
+        }
+      
+        facet[name].range.ranges.push({
+          to: to
+        });
+        
+        return this;
+      },
+
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.RangeFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.RangeFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.RangeFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.RangeFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.RangeFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.RangeFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.RangeFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.RangeFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A statistical facet allows you to compute statistical data over a numeric fields. Statistical data includes
+    the count, total, sum of squares, mean (average), minimum, maximum, variance, and standard deviation.</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.StatisticalFacet
+
+    @desc
+    <p>A facet which returns statistical information about a numeric field</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.StatisticalFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.StatisticalFacet
+        @property {Object} facet
+        */
+    var facet = {};
+
+    facet[name] = {
+      statistical: {}
+    };
+
+    return {
+
+      /**
+            Sets the field to be used to construct the this facet.
+
+            @member ejs.StatisticalFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].statistical.field;
+        }
+      
+        facet[name].statistical.field = fieldName;
+        return this;
+      },
+
+      /**
+            Aggregate statistical info across a set of fields.
+
+            @member ejs.StatisticalFacet
+            @param {Array} aFieldName An array of field names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fields: function (fields) {
+        if (fields == null) {
+          return facet[name].statistical.fields;
+        }
+      
+        if (!isArray(fields)) {
+          throw new TypeError('Argument must be an array');
+        }
+        
+        facet[name].statistical.fields = fields;
+        return this;
+      },
+
+      /**
+            Define a script to evaluate of which the result will be used to generate
+            the statistical information.
+
+            @member ejs.StatisticalFacet
+            @param {String} code The script code to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      script: function (code) {
+        if (code == null) {
+          return facet[name].statistical.script;
+        }
+      
+        facet[name].statistical.script = code;
+        return this;
+      },
+
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.StatisticalFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].statistical.lang;
+        }
+      
+        facet[name].statistical.lang = language;
+        return this;
+      },
+
+      /**
+            Allows you to set script parameters to be used during the execution of the script.
+
+            @member ejs.StatisticalFacet
+            @param {Object} oParams An object containing key/value pairs representing param name/value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (oParams) {
+        if (oParams == null) {
+          return facet[name].statistical.params;
+        }
+      
+        facet[name].statistical.params = oParams;
+        return this;
+      },
+
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.StatisticalFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.StatisticalFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.StatisticalFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.StatisticalFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.StatisticalFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.StatisticalFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.StatisticalFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.StatisticalFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A facet which returns the N most frequent terms within a collection
+       or set of collections. Term facets are useful for building constructs
+       which allow users to refine search results by filtering on terms returned
+       by the facet.</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <p>For more information on faceted navigation, see this Wikipedia article on
+       <a href="http://en.wikipedia.org/wiki/Faceted_classification">Faceted Classification</a></p<
+
+    @name ejs.TermsFacet
+
+    @desc
+    <p>A facet which returns the N most frequent terms within a collection
+       or set of collections.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.TermsFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.TermsFacet
+        @property {Object} facet
+        */
+    var facet = {};
+
+    facet[name] = {
+      terms: {}
+    };
+
+    return {
+
+      /**
+            Sets the field to be used to construct the this facet.  Set to
+            _index to return a facet count of hits per _index the search was 
+            executed on.
+
+            @member ejs.TermsFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].terms.field;
+        }
+      
+        facet[name].terms.field = fieldName;
+        return this;
+      },
+
+      /**
+            Aggregate statistical info across a set of fields.
+
+            @member ejs.TermsFacet
+            @param {Array} aFieldName An array of field names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fields: function (fields) {
+        if (fields == null) {
+          return facet[name].terms.fields;
+        }
+      
+        if (!isArray(fields)) {
+          throw new TypeError('Argument must be an array');
+        }
+        
+        facet[name].terms.fields = fields;
+        return this;
+      },
+
+      /**
+            Sets a script that will provide the terms for a given document.
+
+            @member ejs.TermsFacet
+            @param {String} script The script code.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scriptField: function (script) {
+        if (script == null) {
+          return facet[name].terms.script_field;
+        }
+      
+        facet[name].terms.script_field = script;
+        return this;
+      },
+            
+      /**
+            Sets the number of facet entries that will be returned for this facet. For instance, you
+            might ask for only the top 5 <code>authors</code> although there might be hundreds of
+            unique authors.
+
+            @member ejs.TermsFacet
+            @param {Integer} facetSize The numer of facet entries to be returned.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      size: function (facetSize) {
+        if (facetSize == null) {
+          return facet[name].terms.size;
+        }
+      
+        facet[name].terms.size = facetSize;
+        return this;
+      },
+
+      /**
+            Sets the type of ordering that will be performed on the date
+            buckets.  Valid values are:
+            
+            count - default, sort by the number of items in the bucket
+            term - sort by term value.
+            reverse_count - reverse sort of the number of items in the bucket
+            reverse_term - reverse sort of the term value.
+            
+            @member ejs.TermsFacet
+            @param {String} o The ordering method
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      order: function (o) {
+        if (o == null) {
+          return facet[name].terms.order;
+        }
+      
+        o = o.toLowerCase();
+        if (o === 'count' || o === 'term' || 
+          o === 'reverse_count' || o === 'reverse_term') {
+          
+          facet[name].terms.order = o;
+        }
+        
+        return this;
+      },
+
+      /**
+            <p>Allows you to return all terms, even if the frequency count is 0. This should not be
+               used on fields that contain a large number of unique terms because it could cause
+               <em>out-of-memory</em> errors.</p>
+
+            @member ejs.TermsFacet
+            @param {String} trueFalse <code>true</code> or <code>false</code>
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      allTerms: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].terms.all_terms;
+        }
+      
+        facet[name].terms.all_terms = trueFalse;
+        return this;
+      },
+
+      /**
+            <p>Allows you to filter out unwanted facet entries. When passed
+            a single term, it is appended to the list of currently excluded
+            terms.  If passed an array, it overwrites all existing values.</p>
+
+            @member ejs.TermsFacet
+            @param {String || Array} exclude A single term to exclude or an 
+              array of terms to exclude.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      exclude: function (exclude) {
+        if (facet[name].terms.exclude == null) {
+          facet[name].terms.exclude = [];
+        }
+        
+        if (exclude == null) {
+          return facet[name].terms.exclude;
+        }
+      
+        if (isString(exclude)) {
+          facet[name].terms.exclude.push(exclude);
+        } else if (isArray(exclude)) {
+          facet[name].terms.exclude = exclude;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+        
+        return this;
+      },
+
+      /**
+            <p>Allows you to only include facet entries matching a specified regular expression.</p>
+
+            @member ejs.TermsFacet
+            @param {String} exp A valid regular expression.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      regex: function (exp) {
+        if (exp == null) {
+          return facet[name].terms.regex;
+        }
+      
+        facet[name].terms.regex = exp;
+        return this;
+      },
+
+      /**
+            <p>Allows you to set the regular expression flags to be used
+            with the <code>regex</code></p>
+
+            @member ejs.TermsFacet
+            @param {String} flags A valid regex flag - see <a href="http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html#field_summary">Java Pattern API</a>
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      regexFlags: function (flags) {
+        if (flags == null) {
+          return facet[name].terms.regex_flags;
+        }
+      
+        facet[name].terms.regex_flags = flags;
+        return this;
+      },
+
+      /**
+            Allows you modify the term using a script. The modified value
+            is then used in the facet collection.
+
+            @member ejs.TermsFacet
+            @param {String} scriptCode A valid script string to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      script: function (scriptCode) {
+        if (scriptCode == null) {
+          return facet[name].terms.script;
+        }
+      
+        facet[name].terms.script = scriptCode;
+        return this;
+      },
+
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.TermsFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].terms.lang;
+        }
+      
+        facet[name].terms.lang = language;
+        return this;
+      },
+
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.TermsFacet
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return facet[name].terms.params;
+        }
+    
+        facet[name].terms.params = p;
+        return this;
+      },
+      
+      /**
+            Sets the execution hint determines how the facet is computed.  
+            Currently only supported value is "map".
+
+            @member ejs.TermsFacet
+            @param {Object} h The hint value as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      executionHint: function (h) {
+        if (h == null) {
+          return facet[name].terms.execution_hint;
+        }
+    
+        facet[name].terms.execution_hint = h;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.TermsFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.TermsFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.TermsFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.TermsFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.TermsFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.TermsFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TermsFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> property. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.TermsFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A termsStatsFacet allows you to compute statistics over an aggregate key (term). Essentially this
+    facet provides the functionality of what is often refered to as a <em>pivot table</em>.</p>
+
+    <p>Facets are similar to SQL <code>GROUP BY</code> statements but perform much
+       better. You can also construct several <em>"groups"</em> at once by simply
+       specifying multiple facets.</p>
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            For more information on faceted navigation, see
+            <a href="http://en.wikipedia.org/wiki/Faceted_classification">this</a>
+            Wikipedia article on Faceted Classification.
+        </p>
+    </div>
+
+    @name ejs.TermStatsFacet
+
+    @desc
+    <p>A facet which computes statistical data based on an aggregate key.</p>
+
+    @param {String} name The name which be used to refer to this facet. For instance,
+        the facet itself might utilize a field named <code>doc_authors</code>. Setting
+        <code>name</code> to <code>Authors</code> would allow you to refer to the
+        facet by that name, possibly simplifying some of the display logic.
+
+    */
+  ejs.TermStatsFacet = function (name) {
+
+    /**
+        The internal facet object.
+        @member ejs.TermStatsFacet
+        @property {Object} facet
+        */
+    var facet = {};
+
+    facet[name] = {
+      terms_stats: {}
+    };
+
+    return {
+
+      /**
+            Sets the field for which statistical information will be generated.
+
+            @member ejs.TermStatsFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].terms_stats.value_field;
+        }
+      
+        facet[name].terms_stats.value_field = fieldName;
+        return this;
+      },
+
+      /**
+            Sets the field which will be used to pivot on (group-by).
+
+            @member ejs.TermStatsFacet
+            @param {String} fieldName The field name whose data will be used to construct the facet.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      keyField: function (fieldName) {
+        if (fieldName == null) {
+          return facet[name].terms_stats.key_field;
+        }
+      
+        facet[name].terms_stats.key_field = fieldName;
+        return this;
+      },
+
+      /**
+            Sets a script that will provide the terms for a given document.
+
+            @member ejs.TermStatsFacet
+            @param {String} script The script code.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scriptField: function (script) {
+        if (script == null) {
+          return facet[name].terms_stats.script_field;
+        }
+      
+        facet[name].terms_stats.script_field = script;
+        return this;
+      },
+      
+      /**
+            Define a script to evaluate of which the result will be used to generate
+            the statistical information.
+
+            @member ejs.TermStatsFacet
+            @param {String} code The script code to execute.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      valueScript: function (code) {
+        if (code == null) {
+          return facet[name].terms_stats.value_script;
+        }
+      
+        facet[name].terms_stats.value_script = code;
+        return this;
+      },
+
+      /**
+            <p>Allows you to return all terms, even if the frequency count is 0. This should not be
+               used on fields that contain a large number of unique terms because it could cause
+               <em>out-of-memory</em> errors.</p>
+
+            @member ejs.TermStatsFacet
+            @param {String} trueFalse <code>true</code> or <code>false</code>
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      allTerms: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].terms_stats.all_terms;
+        }
+      
+        facet[name].terms_stats.all_terms = trueFalse;
+        return this;
+      },
+      
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code>, <code>groovy</code>, and <code>mvel</code>.
+
+            @member ejs.TermStatsFacet
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return facet[name].terms_stats.lang;
+        }
+      
+        facet[name].terms_stats.lang = language;
+        return this;
+      },
+
+      /**
+            Allows you to set script parameters to be used during the execution of the script.
+
+            @member ejs.TermStatsFacet
+            @param {Object} oParams An object containing key/value pairs representing param name/value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (oParams) {
+        if (oParams == null) {
+          return facet[name].terms_stats.params;
+        }
+      
+        facet[name].terms_stats.params = oParams;
+        return this;
+      },
+
+      /**
+            Sets the number of facet entries that will be returned for this facet. For instance, you
+            might ask for only the top 5 aggregate keys although there might be hundreds of
+            unique keys. <strong>Higher settings could cause memory strain</strong>.
+
+            @member ejs.TermStatsFacet
+            @param {Integer} facetSize The numer of facet entries to be returned.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      size: function (facetSize) {
+        if (facetSize == null) {
+          return facet[name].terms_stats.size;
+        }
+      
+        facet[name].terms_stats.size = facetSize;
+        return this;
+      },
+
+      /**
+            Sets the type of ordering that will be performed on the date
+            buckets.  Valid values are:
+            
+            count - default, sort by the number of items in the bucket
+            term - sort by term value.
+            reverse_count - reverse sort of the number of items in the bucket
+            reverse_term - reverse sort of the term value.
+            total - sorts by the total value of the bucket contents
+            reverse_total - reverse sort of the total value of bucket contents
+            min - the minimum value in the bucket
+            reverse_min - the reverse sort of the minimum value
+            max - the maximum value in the bucket
+            reverse_max - the reverse sort of the maximum value
+            mean - the mean value of the bucket contents
+            reverse_mean - the reverse sort of the mean value of bucket contents.
+            
+            @member ejs.TermStatsFacet
+            @param {String} o The ordering method
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      order: function (o) {
+        if (o == null) {
+          return facet[name].terms_stats.order;
+        }
+      
+        o = o.toLowerCase();
+        if (o === 'count' || o === 'term' || o === 'reverse_count' || 
+          o === 'reverse_term' || o === 'total' || o === 'reverse_total' || 
+          o === 'min' || o === 'reverse_min' || o === 'max' || 
+          o === 'reverse_max' || o === 'mean' || o === 'reverse_mean') {
+          
+          facet[name].terms_stats.order = o;
+        }
+        
+        return this;
+      },
+
+      /**
+            <p>Allows you to reduce the documents used for computing facet results.</p>
+
+            @member ejs.TermStatsFacet
+            @param {Object} oFilter A valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facetFilter: function (oFilter) {
+        if (oFilter == null) {
+          return facet[name].facet_filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        facet[name].facet_filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            <p>Computes values across the entire index</p>
+
+            @member ejs.TermStatsFacet
+            @param {Boolean} trueFalse Calculate facet counts globally or not.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      global: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].global;
+        }
+        
+        facet[name].global = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Computes values across the the specified scope</p>
+
+            @member ejs.TermStatsFacet
+            @param {String} scope The scope name to calculate facet counts with.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (scope) {
+        if (scope == null) {
+          return facet[name].scope;
+        }
+        
+        facet[name].scope = scope;
+        return this;
+      },
+      
+      /**
+            <p>Enables caching of the <code>facetFilter</code></p>
+
+            @member ejs.TermStatsFacet
+            @param {Boolean} trueFalse If the facetFilter should be cached or not
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheFilter: function (trueFalse) {
+        if (trueFalse == null) {
+          return facet[name].cache_filter;
+        }
+        
+        facet[name].cache_filter = trueFalse;
+        return this;
+      },
+      
+      /**
+            <p>Sets the path to the nested document if faceting against a
+            nested field.</p>
+
+            @member ejs.TermStatsFacet
+            @param {String} path The nested path
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nested: function (path) {
+        if (path == null) {
+          return facet[name].nested;
+        }
+        
+        facet[name].nested = path;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.TermStatsFacet
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(facet);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TermStatsFacet
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'facet';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>facet</code> object. This is typically used by
+               internal API functions so use with caution.</p>
+
+            @member ejs.TermStatsFacet
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return facet;
+      }
+    };
+  };
+
+  /**
+    @class
+    A container Filter that allows Boolean AND composition of Filters.
+
+    @name ejs.AndFilter
+
+    @desc
+    A container Filter that allows Boolean AND composition of Filters.
+
+    @param {Filter || Array} f A single Filter object or an array of valid 
+      Filter objects.
+    */
+  ejs.AndFilter = function (f) {
+
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.AndFilter
+         @property {Object} filter
+         */
+    var i,
+      len,
+      filter = {
+        and: {
+          filters: []
+        }
+      };
+
+    if (isFilter(f)) {
+      filter.and.filters.push(f._self());
+    } else if (isArray(f)) {
+      for (i = 0, len = f.length; i < len; i++) {
+        if (!isFilter(f[i])) {
+          throw new TypeError('Array must contain only Filter objects');
+        }
+        
+        filter.and.filters.push(f[i]._self());
+      }
+    } else {
+      throw new TypeError('Argument must be a Filter or Array of Filters');
+    }
+
+    return {
+
+      /**
+             Sets the filters for the filter.  If fltr is a single 
+             Filter, it is added to the current filters.  If fltr is an array
+             of Filters, then they replace all existing filters.
+
+             @member ejs.AndFilter
+             @param {Filter || Array} fltr A valid filter object or an array of filters.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filters: function (fltr) {
+        var i,
+          len;
+          
+        if (fltr == null) {
+          return filter.and.filters;
+        }
+      
+        if (isFilter(fltr)) {
+          filter.and.filters.push(fltr._self());
+        } else if (isArray(fltr)) {
+          filter.and.filters = [];
+          for (i = 0, len = fltr.length; i < len; i++) {
+            if (!isFilter(fltr[i])) {
+              throw new TypeError('Array must contain only Filter objects');
+            }
+            
+            filter.and.filters.push(fltr[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Filter or an Array of Filters');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.AndFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.and._name;
+        }
+
+        filter.and._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.AndFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.and._cache;
+        }
+
+        filter.and._cache = trueFalse;
+        return this;
+      },
+  
+      /**
+            Sets the cache key.
+
+            @member ejs.AndFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.and._cache_key;
+        }
+
+        filter.and._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.AndFilter
+             @returns {String} JSON representation of the andFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.AndFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.AndFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A <code>BoolFilter</code> allows you to build <em>Boolean</em> filter constructs
+    from individual filters. Similar in concept to Boolean query, except that 
+    the clauses are other filters. Can be placed within queries that accept a 
+    filter.
+  
+    @name ejs.BoolFilter
+
+    @desc
+    A Filter that matches documents matching boolean combinations of other
+    filters.
+
+    */
+  ejs.BoolFilter = function () {
+
+    /**
+         The internal filter object. <code>Use _self()</code>
+         @member ejs.BoolFilter
+         @property {Object} filter
+         */
+    var filter = {
+      bool: {}
+    };
+
+    return {
+
+      /**
+             Adds filter to boolean container. Given filter "must" appear in 
+             matching documents.  If passed a single Filter it is added to the
+             list of existing filters.  If passed an array of Filters, they
+             replace all existing filters.
+
+             @member ejs.BoolFilter
+             @param {Filter || Array} oFilter A valid Filter or array of
+              Filter objects.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      must: function (oFilter) {
+        var i, len;
+        
+        if (filter.bool.must == null) {
+          filter.bool.must = [];
+        }
+    
+        if (oFilter == null) {
+          return filter.bool.must;
+        }
+
+        if (isFilter(oFilter)) {
+          filter.bool.must.push(oFilter._self());
+        } else if (isArray(oFilter)) {
+          filter.bool.must = [];
+          for (i = 0, len = oFilter.length; i < len; i++) {
+            if (!isFilter(oFilter[i])) {
+              throw new TypeError('Argument must be an array of Filters');
+            }
+            
+            filter.bool.must.push(oFilter[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Filter or array of Filters');
+        }
+        
+        return this;
+      },
+
+      /**
+             Adds filter to boolean container. Given filter "must not" appear 
+             in matching documents. If passed a single Filter it is added to 
+             the list of existing filters.  If passed an array of Filters, 
+             they replace all existing filters.
+
+             @member ejs.BoolFilter
+             @param {Filter || Array} oFilter A valid Filter or array of
+               Filter objects.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      mustNot: function (oFilter) {
+        var i, len;
+        
+        if (filter.bool.must_not == null) {
+          filter.bool.must_not = [];
+        }
+
+        if (oFilter == null) {
+          return filter.bool.must_not;
+        }
+    
+        if (isFilter(oFilter)) {
+          filter.bool.must_not.push(oFilter._self());
+        } else if (isArray(oFilter)) {
+          filter.bool.must_not = [];
+          for (i = 0, len = oFilter.length; i < len; i++) {
+            if (!isFilter(oFilter[i])) {
+              throw new TypeError('Argument must be an array of Filters');
+            }
+            
+            filter.bool.must_not.push(oFilter[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Filter or array of Filters');
+        }
+        
+        return this;
+      },
+
+      /**
+             Adds filter to boolean container. Given filter "should" appear in 
+             matching documents. If passed a single Filter it is added to 
+             the list of existing filters.  If passed an array of Filters, 
+             they replace all existing filters.
+
+             @member ejs.BoolFilter
+             @param {Filter || Array} oFilter A valid Filter or array of
+                Filter objects.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      should: function (oFilter) {
+        var i, len;
+        
+        if (filter.bool.should == null) {
+          filter.bool.should = [];
+        }
+
+        if (oFilter == null) {
+          return filter.bool.should;
+        }
+    
+        if (isFilter(oFilter)) {
+          filter.bool.should.push(oFilter._self());
+        } else if (isArray(oFilter)) {
+          filter.bool.should = [];
+          for (i = 0, len = oFilter.length; i < len; i++) {
+            if (!isFilter(oFilter[i])) {
+              throw new TypeError('Argument must be an array of Filters');
+            }
+            
+            filter.bool.should.push(oFilter[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Filter or array of Filters');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.BoolFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.bool._name;
+        }
+
+        filter.bool._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.BoolFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.bool._cache;
+        }
+
+        filter.bool._cache = trueFalse;
+        return this;
+      },
+  
+      /**
+            Sets the cache key.
+
+            @member ejs.BoolFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.bool._cache_key;
+        }
+
+        filter.bool._cache_key = key;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.BoolFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.BoolFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.BoolFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>An existsFilter matches documents where the specified field is present
+    and the field contains a legitimate value.</p>
+
+    @name ejs.ExistsFilter
+
+    @desc
+    Filters documents where a specified field exists and contains a value.
+
+    @param {String} fieldName the field name that must exists and contain a value.
+    */
+  ejs.ExistsFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>get()</code>
+
+         @member ejs.ExistsFilter
+         @property {Object} filter
+         */
+    var filter = {
+      exists: {
+        field: fieldName
+      }
+    };
+
+    return {
+
+      /**
+            Sets the field to check for missing values.
+
+            @member ejs.ExistsFilter
+            @param {String} name A name of the field.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (name) {
+        if (name == null) {
+          return filter.exists.field;
+        }
+
+        filter.exists.field = name;
+        return this;
+      },
+      
+      /**
+            Sets the filter name.
+
+            @member ejs.ExistsFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.exists._name;
+        }
+
+        filter.exists._name = name;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.ExistsFilter
+             @returns {String} JSON representation of the existsFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.ExistsFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.ExistsFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A filter that restricts matched results/docs to a geographic bounding box described by
+    the specified lon and lat coordinates. The format conforms with the GeoJSON specification.</p>
+
+    @name ejs.GeoBboxFilter
+
+    @desc
+    Filter results to those which are contained within the defined bounding box.
+
+    @param {String} fieldName the document property/field containing the Geo Point (lon/lat).
+
+    */
+  ejs.GeoBboxFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.GeoBboxFilter
+         @property {Object} filter
+         */
+    var filter = {
+      geo_bounding_box: {}
+    };
+
+    filter.geo_bounding_box[fieldName] = {};
+
+    return {
+
+      /**
+            Sets the fields to filter against.
+
+            @member ejs.GeoBboxFilter
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = filter.geo_bounding_box[fieldName];
+    
+        if (f == null) {
+          return fieldName;
+        }
+
+        delete filter.geo_bounding_box[fieldName];
+        fieldName = f;
+        filter.geo_bounding_box[f] = oldValue;
+    
+        return this;
+      },
+      
+      /**
+             Sets the top-left coordinate of the bounding box
+
+             @member ejs.GeoBboxFilter
+             @param {GeoPoint} p A valid GeoPoint object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      topLeft: function (p) {
+        if (p == null) {
+          return filter.geo_bounding_box[fieldName].top_left;
+        }
+      
+        if (isGeoPoint(p)) {
+          filter.geo_bounding_box[fieldName].top_left = p._self();
+        } else {
+          throw new TypeError('Argument must be a GeoPoint');
+        }
+        
+        return this;
+      },
+
+      /**
+             Sets the bottom-right coordinate of the bounding box
+
+             @member ejs.GeoBboxFilter
+             @param {GeoPoint} p A valid GeoPoint object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      bottomRight: function (p) {
+        if (p == null) {
+          return filter.geo_bounding_box[fieldName].bottom_right;
+        }
+      
+        if (isGeoPoint(p)) {
+          filter.geo_bounding_box[fieldName].bottom_right = p._self();
+        } else {
+          throw new TypeError('Argument must be a GeoPoint');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the type of the bounding box execution. Valid values are
+            "memory" and "indexed".  Default is memory.
+
+            @member ejs.GeoBboxFilter
+            @param {String} type The execution type as a string.  
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (type) {
+        if (type == null) {
+          return filter.geo_bounding_box.type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'memory' || type === 'indexed') {
+          filter.geo_bounding_box.type = type;
+        }
+        
+        return this;
+      },
+      
+      /**
+            If the lat/long points should be normalized to lie within their
+            respective normalized ranges.
+            
+            Normalized ranges are:
+            lon = -180 (exclusive) to 180 (inclusive) range
+            lat = -90 to 90 (both inclusive) range
+
+            @member ejs.GeoBboxFilter
+            @param {String} trueFalse True if the coordinates should be normalized. False otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      normalize: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_bounding_box.normalize;
+        }
+
+        filter.geo_bounding_box.normalize = trueFalse;
+        return this;
+      },
+      
+      /**
+            Sets the filter name.
+
+            @member ejs.GeoBboxFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.geo_bounding_box._name;
+        }
+
+        filter.geo_bounding_box._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.GeoBboxFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_bounding_box._cache;
+        }
+
+        filter.geo_bounding_box._cache = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets the cache key.
+
+            @member ejs.GeoBboxFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.geo_bounding_box._cache_key;
+        }
+
+        filter.geo_bounding_box._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.GeoBboxFilter
+             @returns {String} JSON representation of the notFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoBboxFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.GeoBboxFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A filter that restricts matched results/docs to a given distance from the
+    point of origin. The format conforms with the GeoJSON specification.</p>
+
+    @name ejs.GeoDistanceFilter
+
+    @desc
+    Filter results to those which fall within the given distance of the point of origin.
+
+    @param {String} fieldName the document property/field containing the Geo Point (lon/lat).
+
+    */
+  ejs.GeoDistanceFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.GeoDistanceFilter
+         @property {Object} filter
+         */
+    var filter = {
+      geo_distance: {
+      }
+    };
+
+    filter.geo_distance[fieldName] = [0, 0];
+    
+    return {
+
+      /**
+            Sets the fields to filter against.
+
+            @member ejs.GeoDistanceFilter
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = filter.geo_distance[fieldName];
+    
+        if (f == null) {
+          return fieldName;
+        }
+
+        delete filter.geo_distance[fieldName];
+        fieldName = f;
+        filter.geo_distance[f] = oldValue;
+    
+        return this;
+      },
+      
+      /**
+             Sets the numeric distance to be used.  The distance can be a 
+             numeric value, and then the unit (either mi or km can be set) 
+             controlling the unit. Or a single string with the unit as well.
+
+             @member ejs.GeoDistanceFilter
+             @param {Number} numericDistance the numeric distance
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      distance: function (numericDistance) {
+        if (numericDistance == null) {
+          return filter.geo_distance.distance;
+        }
+      
+        if (!isNumber(numericDistance)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.geo_distance.distance = numericDistance;
+        return this;
+      },
+
+      /**
+             Sets the distance unit.  Valid values are "mi" for miles or "km"
+             for kilometers. Defaults to "km".
+
+             @member ejs.GeoDistanceFilter
+             @param {Number} unit the unit of distance measure.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      unit: function (unit) {
+        if (unit == null) {
+          return filter.geo_distance.unit;
+        }
+      
+        unit = unit.toLowerCase();
+        if (unit === 'mi' || unit === 'km') {
+          filter.geo_distance.unit = unit;
+        }
+        
+        return this;
+      },
+
+      /**
+             Sets the point of origin in which distance will be measured from
+
+             @member ejs.GeoDistanceFilter
+             @param {GeoPoint} p A valid GeoPoint object.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      point: function (p) {
+        if (p == null) {
+          return filter.geo_distance[fieldName];
+        }
+      
+        if (isGeoPoint(p)) {
+          filter.geo_distance[fieldName] = p._self();
+        } else {
+          throw new TypeError('Argument must be a GeoPoint');
+        }
+        
+        return this;
+      },
+
+
+      /**
+            How to compute the distance. Can either be arc (better precision) 
+            or plane (faster). Defaults to arc.
+
+            @member ejs.GeoDistanceFilter
+            @param {String} type The execution type as a string.  
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      distanceType: function (type) {
+        if (type == null) {
+          return filter.geo_distance.distance_type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'arc' || type === 'plane') {
+          filter.geo_distance.distance_type = type;
+        }
+        
+        return this;
+      },
+      
+      /**
+            If the lat/long points should be normalized to lie within their
+            respective normalized ranges.
+            
+            Normalized ranges are:
+            lon = -180 (exclusive) to 180 (inclusive) range
+            lat = -90 to 90 (both inclusive) range
+
+            @member ejs.GeoDistanceFilter
+            @param {String} trueFalse True if the coordinates should be normalized. False otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      normalize: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_distance.normalize;
+        }
+
+        filter.geo_distance.normalize = trueFalse;
+        return this;
+      },
+      
+      /**
+            Will an optimization of using first a bounding box check will be 
+            used. Defaults to memory which will do in memory checks. Can also 
+            have values of indexed to use indexed value check, or none which 
+            disables bounding box optimization.
+
+            @member ejs.GeoDistanceFilter
+            @param {String} t optimization type of memory, indexed, or none.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      optimizeBbox: function (t) {
+        if (t == null) {
+          return filter.geo_distance.optimize_bbox;
+        }
+
+        t = t.toLowerCase();
+        if (t === 'memory' || t === 'indexed' || t === 'none') {
+          filter.geo_distance.optimize_bbox = t;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the filter name.
+
+            @member ejs.GeoDistanceFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.geo_distance._name;
+        }
+
+        filter.geo_distance._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.GeoDistanceFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_distance._cache;
+        }
+
+        filter.geo_distance._cache = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets the cache key.
+
+            @member ejs.GeoDistanceFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.geo_distance._cache_key;
+        }
+
+        filter.geo_distance._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.GeoDistanceFilter
+             @returns {String} JSON representation of the notFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoDistanceFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.GeoDistanceFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A filter that restricts matched results/docs to a given distance range from the
+    point of origin. The format conforms with the GeoJSON specification.</p>
+
+    @name ejs.GeoDistanceRangeFilter
+
+    @desc
+    Filter results to those which fall within the given distance range of the point of origin.
+
+    @param {String} fieldName the document property/field containing the Geo Point (lon/lat).
+
+    */
+  ejs.GeoDistanceRangeFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.GeoDistanceRangeFilter
+         @property {Object} filter
+         */
+    var filter = {
+      geo_distance_range: {}
+    };
+
+    filter.geo_distance_range[fieldName] = [0, 0];
+    
+    return {
+
+     /**
+            Sets the fields to filter against.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = filter.geo_distance_range[fieldName];
+
+        if (f == null) {
+          return fieldName;
+        }
+
+        delete filter.geo_distance_range[fieldName];
+        fieldName = f;
+        filter.geo_distance_range[f] = oldValue;
+
+        return this;
+      },
+      
+      /**
+             * Sets the start point of the distance range
+
+             @member ejs.GeoDistanceRangeFilter
+             @param {Number} numericDistance the numeric distance
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      from: function (numericDistance) {
+        if (numericDistance == null) {
+          return filter.geo_distance_range.from;
+        }
+      
+        if (!isNumber(numericDistance)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.geo_distance_range.from = numericDistance;
+        return this;
+      },
+
+      /**
+             * Sets the end point of the distance range
+
+             @member ejs.GeoDistanceRangeFilter
+             @param {Number} numericDistance the numeric distance
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      to: function (numericDistance) {
+        if (numericDistance == null) {
+          return filter.geo_distance_range.to;
+        }
+
+        if (!isNumber(numericDistance)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+            
+        filter.geo_distance_range.to = numericDistance;
+        return this;
+      },
+
+      /**
+            Should the first from (if set) be inclusive or not. 
+            Defaults to true
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeLower: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_distance_range.include_lower;
+        }
+
+        filter.geo_distance_range.include_lower = trueFalse;
+        return this;
+      },
+
+      /**
+            Should the last to (if set) be inclusive or not. Defaults to true.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeUpper: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_distance_range.include_upper;
+        }
+
+        filter.geo_distance_range.include_upper = trueFalse;
+        return this;
+      },
+
+      /**
+            Greater than value.  Same as setting from to the value, and 
+            include_lower to false,
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Number} val the numeric distance
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gt: function (val) {
+        if (val == null) {
+          return filter.geo_distance_range.gt;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.geo_distance_range.gt = val;
+        return this;
+      },
+
+      /**
+            Greater than or equal to value.  Same as setting from to the value,
+            and include_lower to true.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Number} val the numeric distance
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gte: function (val) {
+        if (val == null) {
+          return filter.geo_distance_range.gte;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.geo_distance_range.gte = val;
+        return this;
+      },
+
+      /**
+            Less than value.  Same as setting to to the value, and include_upper 
+            to false.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Number} val the numeric distance
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lt: function (val) {
+        if (val == null) {
+          return filter.geo_distance_range.lt;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.geo_distance_range.lt = val;
+        return this;
+      },
+
+      /**
+            Less than or equal to value.  Same as setting to to the value, 
+            and include_upper to true.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Number} val the numeric distance
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lte: function (val) {
+        if (val == null) {
+          return filter.geo_distance_range.lte;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.geo_distance_range.lte = val;
+        return this;
+      },
+      
+      /**
+             Sets the distance unit.  Valid values are "mi" for miles or "km"
+             for kilometers. Defaults to "km".
+
+             @member ejs.GeoDistanceRangeFilter
+             @param {Number} unit the unit of distance measure.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      unit: function (unit) {
+        if (unit == null) {
+          return filter.geo_distance_range.unit;
+        }
+      
+        unit = unit.toLowerCase();
+        if (unit === 'mi' || unit === 'km') {
+          filter.geo_distance_range.unit = unit;
+        }
+        
+        return this;
+      },
+
+      /**
+             Sets the point of origin in which distance will be measured from
+
+             @member ejs.GeoDistanceRangeFilter
+             @param {GeoPoint} p A valid GeoPoint object.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      point: function (p) {
+        if (p == null) {
+          return filter.geo_distance_range[fieldName];
+        }
+      
+        if (isGeoPoint(p)) {
+          filter.geo_distance_range[fieldName] = p._self();
+        } else {
+          throw new TypeError('Argument must be a GeoPoint');
+        }
+        
+        return this;
+      },
+
+
+      /**
+            How to compute the distance. Can either be arc (better precision) 
+            or plane (faster). Defaults to arc.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {String} type The execution type as a string.  
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      distanceType: function (type) {
+        if (type == null) {
+          return filter.geo_distance_range.distance_type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'arc' || type === 'plane') {
+          filter.geo_distance_range.distance_type = type;
+        }
+        
+        return this;
+      },
+      
+      /**
+            If the lat/long points should be normalized to lie within their
+            respective normalized ranges.
+            
+            Normalized ranges are:
+            lon = -180 (exclusive) to 180 (inclusive) range
+            lat = -90 to 90 (both inclusive) range
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {String} trueFalse True if the coordinates should be normalized. False otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      normalize: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_distance_range.normalize;
+        }
+
+        filter.geo_distance_range.normalize = trueFalse;
+        return this;
+      },
+      
+      /**
+            Will an optimization of using first a bounding box check will be 
+            used. Defaults to memory which will do in memory checks. Can also 
+            have values of indexed to use indexed value check, or none which 
+            disables bounding box optimization.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {String} t optimization type of memory, indexed, or none.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      optimizeBbox: function (t) {
+        if (t == null) {
+          return filter.geo_distance_range.optimize_bbox;
+        }
+
+        t = t.toLowerCase();
+        if (t === 'memory' || t === 'indexed' || t === 'none') {
+          filter.geo_distance_range.optimize_bbox = t;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the filter name.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.geo_distance_range._name;
+        }
+
+        filter.geo_distance_range._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_distance_range._cache;
+        }
+
+        filter.geo_distance_range._cache = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets the cache key.
+
+            @member ejs.GeoDistanceRangeFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.geo_distance_range._cache_key;
+        }
+
+        filter.geo_distance_range._cache_key = key;
+        return this;
+      },
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.GeoDistanceRangeFilter
+             @returns {String} JSON representation of the notFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoDistanceRangeFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.GeoDistanceRangeFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A filter for locating documents that fall within a polygon of points. Simply provide a lon/lat
+    for each document as a Geo Point type. The format conforms with the GeoJSON specification.</p>
+
+    @name ejs.GeoPolygonFilter
+
+    @desc
+    Filter results to those which are contained within the polygon of points.
+
+    @param {String} fieldName the document property/field containing the Geo Point (lon/lat).
+    */
+  ejs.GeoPolygonFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.GeoPolygonFilter
+         @property {Object} filter
+         */
+    var filter = {
+      geo_polygon: {}
+    };
+
+    filter.geo_polygon[fieldName] = {
+      points: []
+    };
+
+    return {
+
+      /**
+           Sets the fields to filter against.
+
+           @member ejs.GeoPolygonFilter
+           @param {String} f A valid field name.
+           @returns {Object} returns <code>this</code> so that calls can be chained.
+           */
+      field: function (f) {
+        var oldValue = filter.geo_polygon[fieldName];
+
+        if (f == null) {
+          return fieldName;
+        }
+
+        delete filter.geo_polygon[fieldName];
+        fieldName = f;
+        filter.geo_polygon[f] = oldValue;
+
+        return this;
+      },
+       
+      /**
+             Sets a series of points that represent a polygon.  If passed a 
+             single <code>GeoPoint</code> object, it is added to the current 
+             list of points.  If passed an array of <code>GeoPoint</code> 
+             objects it replaces all current values. 
+
+             @member ejs.GeoPolygonFilter
+             @param {Array} pointsArray the array of points that represent the polygon
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      points: function (p) {
+        var i, len;
+        
+        if (p == null) {
+          return filter.geo_polygon[fieldName].points;
+        }
+      
+        if (isGeoPoint(p)) {
+          filter.geo_polygon[fieldName].points.push(p._self());
+        } else if (isArray(p)) {
+          filter.geo_polygon[fieldName].points = [];
+          for (i = 0, len = p.length; i < len; i++) {
+            if (!isGeoPoint(p[i])) {
+              throw new TypeError('Argument must be Array of GeoPoints');
+            }
+            
+            filter.geo_polygon[fieldName].points.push(p[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a GeoPoint or Array of GeoPoints');
+        }
+        
+        return this;
+      },
+
+      /**
+            If the lat/long points should be normalized to lie within their
+            respective normalized ranges.
+            
+            Normalized ranges are:
+            lon = -180 (exclusive) to 180 (inclusive) range
+            lat = -90 to 90 (both inclusive) range
+
+            @member ejs.GeoPolygonFilter
+            @param {String} trueFalse True if the coordinates should be normalized. False otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      normalize: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_polygon.normalize;
+        }
+
+        filter.geo_polygon.normalize = trueFalse;
+        return this;
+      },
+      
+      /**
+            Sets the filter name.
+
+            @member ejs.GeoPolygonFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.geo_polygon._name;
+        }
+
+        filter.geo_polygon._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.GeoPolygonFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_polygon._cache;
+        }
+
+        filter.geo_polygon._cache = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets the cache key.
+
+            @member ejs.GeoPolygonFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.geo_polygon._cache_key;
+        }
+
+        filter.geo_polygon._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.GeoPolygonFilter
+             @returns {String} JSON representation of the notFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoPolygonFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.GeoPolygonFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Efficient filtering of documents containing shapes indexed using the 
+    geo_shape type.</p>
+
+    <p>Much like the geo_shape type, the geo_shape filter uses a grid square 
+    representation of the filter shape to find those documents which have shapes 
+    that relate to the filter shape in a specified way. In order to do this, the 
+    field being queried must be of geo_shape type. The filter will use the same 
+    PrefixTree configuration as defined for the field.</p>
+
+    @name ejs.GeoShapeFilter
+
+    @desc
+    A Filter to find documents with a geo_shapes matching a specific shape.
+
+    */
+  ejs.GeoShapeFilter = function (field) {
+
+    /**
+         The internal filter object. <code>Use _self()</code>
+         @member ejs.GeoShapeFilter
+         @property {Object} GeoShapeFilter
+         */
+    var filter = {
+      geo_shape: {}
+    };
+
+    filter.geo_shape[field] = {};
+
+    return {
+
+      /**
+            Sets the field to filter against.
+
+            @member ejs.GeoShapeFilter
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = filter.geo_shape[field];
+  
+        if (f == null) {
+          return field;
+        }
+
+        delete filter.geo_shape[field];
+        field = f;
+        filter.geo_shape[f] = oldValue;
+  
+        return this;
+      },
+
+      /**
+            Sets the shape
+
+            @member ejs.GeoShapeFilter
+            @param {String} shape A valid <code>Shape</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      shape: function (shape) {
+        if (shape == null) {
+          return filter.geo_shape[field].shape;
+        }
+
+        if (filter.geo_shape[field].indexed_shape != null) {
+          delete filter.geo_shape[field].indexed_shape;
+        }
+      
+        filter.geo_shape[field].shape = shape._self();
+        return this;
+      },
+
+      /**
+            Sets the indexed shape.  Use this if you already have shape definitions
+            already indexed.
+
+            @member ejs.GeoShapeFilter
+            @param {String} indexedShape A valid <code>IndexedShape</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      indexedShape: function (indexedShape) {
+        if (indexedShape == null) {
+          return filter.geo_shape[field].indexed_shape;
+        }
+
+        if (filter.geo_shape[field].shape != null) {
+          delete filter.geo_shape[field].shape;
+        }
+      
+        filter.geo_shape[field].indexed_shape = indexedShape._self();
+        return this;
+      },
+
+      /**
+            Sets the shape relation type.  A relationship between a Query Shape 
+            and indexed Shapes that will be used to determine if a Document 
+            should be matched or not.  Valid values are:  intersects, disjoint,
+            and within.
+
+            @member ejs.GeoShapeFilter
+            @param {String} indexedShape A valid <code>IndexedShape</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      relation: function (relation) {
+        if (relation == null) {
+          return filter.geo_shape[field].relation;
+        }
+
+        relation = relation.toLowerCase();
+        if (relation === 'intersects' || relation === 'disjoint' || relation === 'within') {
+          filter.geo_shape[field].relation = relation;
+        }
+    
+        return this;
+      },
+          
+      /**
+            Sets the filter name.
+
+            @member ejs.GeoShapeFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.geo_shape._name;
+        }
+
+        filter.geo_shape._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.GeoShapeFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.geo_shape._cache;
+        }
+
+        filter.geo_shape._cache = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets the cache key.
+
+            @member ejs.GeoShapeFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.geo_shape._cache_key;
+        }
+
+        filter.geo_shape._cache_key = key;
+        return this;
+      },
+        
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.GeoShapeFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoShapeFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.GeoShapeFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The has_child filter results in parent documents that have child docs 
+    matching the query being returned.</p>
+
+    @name ejs.HasChildFilter
+
+    @desc
+    Returns results that have child documents matching the filter.
+
+    @param {Object} qry A valid query object.
+    @param {String} type The child type
+    */
+  ejs.HasChildFilter = function (qry, type) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('No Query object found');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.HasChildFilter
+         @property {Object} query
+         */
+    var filter = {
+      has_child: {
+        query: qry._self(),
+        type: type
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query
+
+            @member ejs.HasChildFilter
+            @param {Query} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return filter.has_child.query;
+        }
+  
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query object');
+        }
+        
+        filter.has_child.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the child document type to search against
+
+            @member ejs.HasChildFilter
+            @param {String} t A valid type name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (t) {
+        if (t == null) {
+          return filter.has_child.type;
+        }
+  
+        filter.has_child.type = t;
+        return this;
+      },
+
+      /**
+            Sets the scope of the filter.  A scope allows to run facets on the 
+            same scope name that will work against the child documents. 
+
+            @member ejs.HasChildFilter
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return filter.has_child._scope;
+        }
+  
+        filter.has_child._scope = s;
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.HasChildFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.has_child._name;
+        }
+
+        filter.has_child._name = name;
+        return this;
+      },
+          
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.HasChildFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.HasChildFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.HasChildFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The has_parent results in child documents that have parent docs matching 
+    the query being returned.</p>
+
+    @name ejs.HasParentFilter
+
+    @desc
+    Returns results that have parent documents matching the filter.
+
+    @param {Object} qry A valid query object.
+    @param {String} parentType The child type
+    */
+  ejs.HasParentFilter = function (qry, parentType) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('No Query object found');
+    }
+    
+    /**
+         The internal filter object. <code>Use _self()</code>
+         @member ejs.HasParentFilter
+         @property {Object} query
+         */
+    var filter = {
+      has_parent: {
+        query: qry._self(),
+        parent_type: parentType
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query
+
+            @member ejs.HasParentFilter
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return filter.has_parent.query;
+        }
+
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query object');
+        }
+        
+        filter.has_parent.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the child document type to search against
+
+            @member ejs.HasParentFilter
+            @param {String} t A valid type name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      parentType: function (t) {
+        if (t == null) {
+          return filter.has_parent.parent_type;
+        }
+
+        filter.has_parent.parent_type = t;
+        return this;
+      },
+
+      /**
+            Sets the scope of the filter.  A scope allows to run facets on the 
+            same scope name that will work against the parent documents. 
+
+            @member ejs.HasParentFilter
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return filter.has_parent._scope;
+        }
+
+        filter.has_parent._scope = s;
+        return this;
+      },
+    
+      /**
+            Sets the filter name.
+
+            @member ejs.HasParentFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.has_parent._name;
+        }
+
+        filter.has_parent._name = name;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.HasParentFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.HasParentFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.HasParentFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filters documents that only have the provided ids. Note, this filter 
+    does not require the _id field to be indexed since it works using the 
+    _uid field.</p>
+
+    @name ejs.IdsFilter
+
+    @desc
+    Matches documents with the specified id(s).
+
+    @param {Array || String} ids A single document id or a list of document ids.
+    */
+  ejs.IdsFilter = function (ids) {
+
+    /**
+         The internal filter object. <code>Use get()</code>
+         @member ejs.IdsFilter
+         @property {Object} filter
+         */
+    var filter = {
+      ids: {}
+    };
+  
+    if (isString(ids)) {
+      filter.ids.values = [ids];
+    } else if (isArray(ids)) {
+      filter.ids.values = ids;
+    } else {
+      throw new TypeError('Argument must be a string or an array');
+    }
+
+    return {
+
+      /**
+            Sets the values array or adds a new value. if val is a string, it
+            is added to the list of existing document ids.  If val is an
+            array it is set as the document values and replaces any existing values.
+
+            @member ejs.IdsFilter
+            @param {Array || String} val An single document id or an array of document ids.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      values: function (val) {
+        if (val == null) {
+          return filter.ids.values;
+        }
+  
+        if (isString(val)) {
+          filter.ids.values.push(val);
+        } else if (isArray(val)) {
+          filter.ids.values = val;
+        } else {
+          throw new TypeError('Argument must be a string or an array');
+        }
+      
+        return this;
+      },
+
+      /**
+            Sets the type as a single type or an array of types.  If type is a
+            string, it is added to the list of existing types.  If type is an
+            array, it is set as the types and overwrites an existing types. This
+            parameter is optional.
+
+            @member ejs.IdsFilter
+            @param {Array || String} type A type or a list of types
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (type) {
+        if (filter.ids.type == null) {
+          filter.ids.type = [];
+        }
+      
+        if (type == null) {
+          return filter.ids.type;
+        }
+      
+        if (isString(type)) {
+          filter.ids.type.push(type);
+        } else if (isArray(type)) {
+          filter.ids.type = type;
+        } else {
+          throw new TypeError('Argument must be a string or an array');
+        }
+      
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.IdsFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.ids._name;
+        }
+
+        filter.ids._name = name;
+        return this;
+      },
+             
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.IdsFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.IdsFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.IdsFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The indices filter can be used when executed across multiple indices, 
+    allowing to have a filter that executes only when executed on an index that 
+    matches a specific list of indices, and another filter that executes when it 
+    is executed on an index that does not match the listed indices.</p>
+
+    @name ejs.IndicesFilter
+
+    @desc
+    A configurable filter that is dependent on the index name.
+
+    @param {Object} fltr A valid filter object.
+    @param {String || Array} indices a single index name or an array of index 
+      names.
+    */
+  ejs.IndicesFilter = function (fltr, indices) {
+
+    if (!isFilter(fltr)) {
+      throw new TypeError('Argument must be a Filter');
+    }
+  
+    /**
+         The internal filter object. <code>Use _self()</code>
+         @member ejs.IndicesFilter
+         @property {Object} filter
+         */
+    var filter = {
+      indices: {
+        filter: fltr._self()
+      }
+    };
+
+    if (isString(indices)) {
+      filter.indices.indices = [indices];
+    } else if (isArray(indices)) {
+      filter.indices.indices = indices;
+    } else {
+      throw new TypeError('Argument must be a string or array');
+    }
+
+    return {
+
+      /**
+            Sets the indicies the filter should match.  When passed a string,
+            the index name is added to the current list of indices.  When passed
+            an array, it overwites all current indices.
+
+            @member ejs.IndicesFilter
+            @param {String || Array} i A single index name or an array of index names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      indices: function (i) {
+        if (i == null) {
+          return filter.indices.indices;
+        }
+
+        if (isString(i)) {
+          filter.indices.indices.push(i);
+        } else if (isArray(i)) {
+          filter.indices.indices = i;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+
+        return this;
+      },
+  
+      /**
+            Sets the filter to be used when executing on one of the indicies 
+            specified.
+
+            @member ejs.IndicesFilter
+            @param {Object} f A valid Filter object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      filter: function (f) {
+        if (f == null) {
+          return filter.indices.filter;
+        }
+
+        if (!isFilter(f)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+      
+        filter.indices.filter = f._self();
+        return this;
+      },
+
+      /**
+            Sets the filter to be used on an index that does not match an index
+            name in the indices list.  Can also be set to "none" to not match any
+            documents or "all" to match all documents.
+
+            @member ejs.IndicesFilter
+            @param {Object || String} f A valid Filter object or "none" or "all"
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      noMatchFilter: function (f) {
+        if (f == null) {
+          return filter.indices.no_match_filter;
+        }
+
+        if (isString(f)) {
+          f = f.toLowerCase();
+          if (f === 'none' || f === 'all') {
+            filter.indices.no_match_filter = f;
+          }
+        } else if (isFilter(f)) {
+          filter.indices.no_match_filter = f._self();
+        } else {
+          throw new TypeError('Argument must be string or Filter');
+        }
+    
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.IndicesFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.IndicesFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.IndicesFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A limit filter limits the number of documents (per shard) to execute on.</p>
+
+    @name ejs.LimitFilter
+
+    @desc
+    Limits the number of documents to execute on.
+
+    @param {Integer} limit The number of documents to execute on.
+    */
+  ejs.LimitFilter = function (limit) {
+
+    /**
+         The internal filter object. <code>Use get()</code>
+         @member ejs.LimitFilter
+         @property {Object} filter
+         */
+    var filter = {
+      limit: {
+        value: limit
+      }
+    };
+
+    return {
+
+      /**
+            Sets the limit value.
+
+            @member ejs.LimitFilter
+            @param {Integer} val An The number of documents to execute on.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      value: function (val) {
+        if (val == null) {
+          return filter.limit.value;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+            
+        filter.limit.value = val;
+        return this;
+      },
+           
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.LimitFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.LimitFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.LimitFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>This filter can be used to match on all the documents
+    in a given set of collections and/or types.</p>
+
+    @name ejs.MatchAllFilter
+
+    @desc
+    <p>A filter that matches on all documents</p>
+
+     */
+  ejs.MatchAllFilter = function () {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.MatchAllFilter
+         @property {Object} filter
+         */
+    var filter = {
+      match_all: {}
+    };
+
+    return {
+
+      /**
+             Serializes the internal <em>filter</em> object as a JSON string.
+             @member ejs.MatchAllFilter
+             @returns {String} Returns a JSON representation of the object.
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MatchAllFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            This method is used to retrieve the raw filter object. It's designed
+            for internal use when composing and serializing queries.
+            @member ejs.MatchAllFilter
+            @returns {Object} Returns the object's <em>filter</em> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>An missingFilter matches documents where the specified field contains no legitimate value.</p>
+
+    @name ejs.MissingFilter
+
+    @desc
+    Filters documents where a specific field has no value present.
+
+    @param {String} fieldName the field name to check for missing values.
+    */
+  ejs.MissingFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>get()</code>
+
+         @member ejs.MissingFilter
+         @property {Object} filter
+         */
+    var filter = {
+      missing: {
+        field: fieldName
+      }
+    };
+
+    return {
+
+      /**
+            Sets the field to check for missing values.
+
+            @member ejs.MissingFilter
+            @param {String} name A name of the field.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (name) {
+        if (name == null) {
+          return filter.missing.field;
+        }
+
+        filter.missing.field = name;
+        return this;
+      },
+      
+      /**
+            Checks if the field doesn't exist.
+
+            @member ejs.MissingFilter
+            @param {Boolean} trueFalse True to check if the field doesn't exist.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      existence: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.missing.existence;
+        }
+
+        filter.missing.existence = trueFalse;
+        return this;
+      },
+
+      /**
+            Checks if the field has null values.
+
+            @member ejs.MissingFilter
+            @param {Boolean} trueFalse True to check if the field has nulls.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      nullValue: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.missing.null_value;
+        }
+
+        filter.missing.null_value = trueFalse;
+        return this;
+      },
+            
+      /**
+            Sets the filter name.
+
+            @member ejs.MissingFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.missing._name;
+        }
+
+        filter.missing._name = name;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.MissingFilter
+             @returns {String} JSON representation of the missingFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MissingFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.MissingFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Nested filters allow you to search against content within objects that are
+       embedded inside of other objects. It is similar to <code>XPath</code> 
+       expressions in <code>XML</code> both conceptually and syntactically.</p>
+
+    <p>
+    The filter is executed against the nested objects / docs as if they were 
+    indexed as separate docs and resulting in the root 
+    parent doc (or parent nested mapping).</p>
+  
+    @name ejs.NestedFilter
+
+    @desc
+    <p>Constructs a filter that is capable of executing a filter against objects
+       nested within a document.</p>
+
+    @param {String} path The nested object path.
+
+     */
+  ejs.NestedFilter = function (path) {
+
+    /**
+         The internal Filter object. Use <code>_self()</code>.
+         @member ejs.NestedFilter
+         @property {Object} filter
+         */
+    var filter = {
+      nested: {
+        path: path
+      }
+    };
+
+    return {
+    
+      /**
+             Sets the root context for the nested filter.
+             @member ejs.NestedFilter
+             @param {String} p The path defining the root for the nested filter.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      path: function (p) {
+        if (p == null) {
+          return filter.nested.path;
+        }
+    
+        filter.nested.path = p;
+        return this;
+      },
+
+      /**
+             Sets the nested query to be executed.
+             @member ejs.NestedFilter
+             @param {Query} oQuery A valid Query object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      query: function (oQuery) {
+        if (oQuery == null) {
+          return filter.nested.query;
+        }
+    
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query object');
+        }
+        
+        filter.nested.query = oQuery._self();
+        return this;
+      },
+
+
+      /**
+             Sets the nested filter to be executed.
+             @member ejs.NestedFilter
+             @param {Object} oFilter A valid Filter object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filter: function (oFilter) {
+        if (oFilter == null) {
+          return filter.nested.filter;
+        }
+    
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter object');
+        }
+        
+        filter.nested.filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            Sets the boost value of the nested <code>Query</code>.
+
+            @member ejs.NestedFilter
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return filter.nested.boost;
+        }
+
+        filter.nested.boost = boost;
+        return this;
+      },
+    
+      /**
+            Sets the scope of the filter.  A scope allows to run facets on the 
+            same scope name that will work against the nested documents. 
+
+            @member ejs.NestedFilter
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return filter.nested._scope;
+        }
+
+        filter.nested._scope = s;
+        return this;
+      },
+    
+      /**
+            Sets the filter name.
+
+            @member ejs.NestedFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.nested._name;
+        }
+
+        filter.nested._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.NestedFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.nested._cache;
+        }
+
+        filter.nested._cache = trueFalse;
+        return this;
+      },
+  
+      /**
+            Sets the cache key.
+
+            @member ejs.NestedFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.nested._cache_key;
+        }
+
+        filter.nested._cache_key = key;
+        return this;
+      },
+    
+      /**
+             Serializes the internal <em>filter</em> object as a JSON string.
+             @member ejs.NestedFilter
+             @returns {String} Returns a JSON representation of the termFilter object.
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.NestedFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            This method is used to retrieve the raw filter object. It's designed
+            for internal use when composing and serializing filters.
+            
+            @member ejs.NestedFilter
+            @returns {Object} Returns the object's <em>filter</em> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A container Filter that excludes the documents matched by the
+    contained filter.</p>
+
+    @name ejs.NotFilter
+
+    @desc
+    Container filter that excludes the matched documents of the contained filter.
+
+    @param {Object} oFilter a valid Filter object such as a termFilter, etc.
+    */
+  ejs.NotFilter = function (oFilter) {
+
+    if (!isFilter(oFilter)) {
+      throw new TypeError('Argument must be a Filter');
+    }
+    
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.NotFilter
+         @property {Object} filter
+         */
+    var filter = {
+      not: oFilter._self()
+    };
+
+    return {
+
+      /**
+             Sets the filter
+
+             @member ejs.NotFilter
+             @param {Object} fltr A valid filter object such as a termFilter, etc.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filter: function (fltr) {
+        if (fltr == null) {
+          return filter.not;
+        }
+      
+        if (!isFilter(fltr)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        filter.not = fltr._self();
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.NotFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.not._name;
+        }
+
+        filter.not._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.NotFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.not._cache;
+        }
+
+        filter.not._cache = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets the cache key.
+
+            @member ejs.NotFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.not._cache_key;
+        }
+
+        filter.not._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.NotFilter
+             @returns {String} JSON representation of the notFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.NotFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.NotFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filters documents with fields that have values within a certain numeric 
+    range. Similar to range filter, except that it works only with numeric 
+    values, and the filter execution works differently.</p>
+    
+    <p>The numeric range filter works by loading all the relevant field values 
+    into memory, and checking for the relevant docs if they satisfy the range 
+    requirements. This requires more memory since the numeric range data are 
+    loaded to memory, but can provide a significant increase in performance.</p> 
+    
+    <p>Note, if the relevant field values have already been loaded to memory, 
+    for example because it was used in facets or was sorted on, then this 
+    filter should be used.</p>
+
+    @name ejs.NumericRangeFilter
+
+    @desc
+    A Filter that only accepts numeric values within a specified range.
+
+    @param {string} fieldName The name of the field to filter on.
+    */
+  ejs.NumericRangeFilter = function (fieldName) {
+
+    /**
+         The internal filter object. Use <code>get()</code>
+
+         @member ejs.NumericRangeFilter
+         @property {Object} filter
+         */
+    var filter = {
+      numeric_range: {}
+    };
+
+    filter.numeric_range[fieldName] = {};
+
+    return {
+
+      /**
+             Returns the field name used to create this object.
+
+             @member ejs.NumericRangeFilter
+             @param {String} field the field name
+             @returns {Object} returns <code>this</code> so that calls can be 
+              chained. Returns {String}, field name when field is not specified.
+             */
+      field: function (field) {
+        var oldValue = filter.numeric_range[fieldName];
+      
+        if (field == null) {
+          return fieldName;
+        }
+      
+        delete filter.numeric_range[fieldName];
+        fieldName = field;
+        filter.numeric_range[fieldName] = oldValue;
+      
+        return this;
+      },
+      
+      /**
+             Sets the endpoint for the current range.
+
+             @member ejs.NumericRangeFilter
+             @param {Number} startPoint A numeric value representing the start of the range
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      from: function (from) {
+        if (from == null) {
+          return filter.numeric_range[fieldName].from;
+        }
+        
+        if (!isNumber(from)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.numeric_range[fieldName].from = from;
+        return this;
+      },
+
+      /**
+             Sets the endpoint for the current range.
+
+             @member ejs.NumericRangeFilter
+             @param {Number} endPoint A numeric value representing the end of the range
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      to: function (to) {
+        if (to == null) {
+          return filter.numeric_range[fieldName].to;
+        }
+
+        if (!isNumber(to)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.numeric_range[fieldName].to = to;
+        return this;
+      },
+
+      /**
+            Should the first from (if set) be inclusive or not. 
+            Defaults to true
+
+            @member ejs.NumericRangeFilter
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeLower: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.numeric_range[fieldName].include_lower;
+        }
+
+        filter.numeric_range[fieldName].include_lower = trueFalse;
+        return this;
+      },
+
+      /**
+            Should the last to (if set) be inclusive or not. Defaults to true.
+
+            @member ejs.NumericRangeFilter
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeUpper: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.numeric_range[fieldName].include_upper;
+        }
+
+        filter.numeric_range[fieldName].include_upper = trueFalse;
+        return this;
+      },
+
+      /**
+            Greater than value.  Same as setting from to the value, and 
+            include_lower to false,
+
+            @member ejs.NumericRangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gt: function (val) {
+        if (val == null) {
+          return filter.numeric_range[fieldName].gt;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.numeric_range[fieldName].gt = val;
+        return this;
+      },
+
+      /**
+            Greater than or equal to value.  Same as setting from to the value,
+            and include_lower to true.
+
+            @member ejs.NumericRangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gte: function (val) {
+        if (val == null) {
+          return filter.numeric_range[fieldName].gte;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.numeric_range[fieldName].gte = val;
+        return this;
+      },
+
+      /**
+            Less than value.  Same as setting to to the value, and include_upper 
+            to false.
+
+            @member ejs.NumericRangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lt: function (val) {
+        if (val == null) {
+          return filter.numeric_range[fieldName].lt;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.numeric_range[fieldName].lt = val;
+        return this;
+      },
+
+      /**
+            Less than or equal to value.  Same as setting to to the value, 
+            and include_upper to true.
+
+            @member ejs.NumericRangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lte: function (val) {
+        if (val == null) {
+          return filter.numeric_range[fieldName].lte;
+        }
+
+        if (!isNumber(val)) {
+          throw new TypeError('Argument must be a numeric value');
+        }
+        
+        filter.numeric_range[fieldName].lte = val;
+        return this;
+      },
+                          
+      /**
+            Sets the filter name.
+
+            @member ejs.NumericRangeFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.numeric_range._name;
+        }
+
+        filter.numeric_range._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.NumericRangeFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.numeric_range._cache;
+        }
+
+        filter.numeric_range._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.NumericRangeFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.numeric_range._cache_key;
+        }
+
+        filter.numeric_range._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string.
+
+             @member ejs.NumericRangeFilter
+             @returns {String} JSON representation of the numericRangeFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.NumericRangeFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.NumericRangeFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    A container filter that allows Boolean OR composition of filters.
+
+    @name ejs.OrFilter
+
+    @desc
+    A container Filter that allows Boolean OR composition of filters.
+
+    @param {Filter || Array} filters A valid Filter or array of Filters.
+    */
+  ejs.OrFilter = function (filters) {
+
+    /**
+         The internal filter object. Use <code>_self()</code>
+
+         @member ejs.OrFilter
+         @property {Object} filter
+         */
+    var filter, i, len;
+
+    filter = {
+      or: {
+        filters: []
+      }
+    };
+
+    if (isFilter(filters)) {
+      filter.or.filters.push(filters._self());
+    } else if (isArray(filters)) {
+      for (i = 0, len = filters.length; i < len; i++) {
+        if (!isFilter(filters[i])) {
+          throw new TypeError('Argument must be array of Filters');
+        }
+        
+        filter.or.filters.push(filters[i]._self());
+      }
+    } else {
+      throw new TypeError('Argument must be a Filter or array of Filters');
+    }
+
+    return {
+
+      /**
+             Updates the filters.  If passed a single Filter it is added to 
+             the existing filters.  If passed an array of Filters, they 
+             replace all existing Filters.
+
+             @member ejs.OrFilter
+             @param {Filter || Array} fltr A Filter or array of Filters
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filters: function (fltr) {
+        var i, len;
+        
+        if (fltr == null) {
+          return filter.or.filters;
+        }
+      
+        if (isFilter(fltr)) {
+          filter.or.filters.push(fltr._self());
+        } else if (isArray(fltr)) {
+          filter.or.filters = [];
+          for (i = 0, len = fltr.length; i < len; i++) {
+            if (!isFilter(fltr[i])) {
+              throw new TypeError('Argument must be an array of Filters');
+            }
+            
+            filter.or.filters.push(fltr[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Filter or array of Filters');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.OrFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.or._name;
+        }
+
+        filter.or._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.OrFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.or._cache;
+        }
+
+        filter.or._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.OrFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.or._cache_key;
+        }
+
+        filter.or._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.OrFilter
+             @returns {String} JSON representation of the orFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.OrFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.OrFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filters documents that have fields containing terms with a specified prefix (not analyzed). Similar
+    to phrase query, except that it acts as a filter. Can be placed within queries that accept a filter.</p>
+
+    @name ejs.PrefixFilter
+
+    @desc
+    Filters documents that have fields containing terms with a specified prefix.
+
+    @param {String} fieldName the field name to be used during matching.
+    @param {String} prefix the prefix value.
+    */
+  ejs.PrefixFilter = function (fieldName, prefix) {
+
+    /**
+         The internal filter object. Use <code>get()</code>
+
+         @member ejs.PrefixFilter
+         @property {Object} filter
+         */
+    var filter = {
+      prefix: {}
+    };
+
+    filter.prefix[fieldName] = prefix;
+    
+    return {
+
+      /**
+             Returns the field name used to create this object.
+
+             @member ejs.PrefixFilter
+             @param {String} field the field name
+             @returns {Object} returns <code>this</code> so that calls can be 
+              chained. Returns {String}, field name when field is not specified.
+             */
+      field: function (field) {
+        var oldValue = filter.prefix[fieldName];
+      
+        if (field == null) {
+          return fieldName;
+        }
+      
+        delete filter.prefix[fieldName];
+        fieldName = field;
+        filter.prefix[fieldName] = oldValue;
+      
+        return this;
+      },
+      
+      /**
+             Sets the prefix to search for.
+
+             @member ejs.PrefixFilter
+             @param {String} value the prefix value to match
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      prefix: function (value) {
+        if (value == null) {
+          return filter.prefix[fieldName];
+        }
+      
+        filter.prefix[fieldName] = value;
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.PrefixFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.prefix._name;
+        }
+
+        filter.prefix._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.PrefixFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.prefix._cache;
+        }
+
+        filter.prefix._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.PrefixFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.prefix._cache_key;
+        }
+
+        filter.prefix._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.PrefixFilter
+             @returns {String} JSON representation of the prefixFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.PrefixFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.PrefixFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Wraps any query to be used as a filter. Can be placed within queries 
+    that accept a filter.</p>
+
+    <p>The result of the filter is not cached by default.  Set the cache 
+    parameter to true to cache the result of the filter. This is handy when the 
+    same query is used on several (many) other queries.</p> 
+  
+    <p>Note, the process of caching the first execution is higher when not 
+    caching (since it needs to satisfy different queries).</p>
+  
+    @name ejs.QueryFilter
+
+    @desc
+    Filters documents matching the wrapped query.
+
+    @param {Object} qry A valid query object.
+    */
+  ejs.QueryFilter = function (qry) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.QueryFilter
+         @property {Object} query
+         */
+    var filter = {
+      fquery: {
+        query: qry._self()
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query
+
+            @member ejs.QueryFilter
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return filter.fquery.query;
+        }
+
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        filter.fquery.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.QueryFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.fquery._name;
+        }
+
+        filter.fquery._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.QueryFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.fquery._cache;
+        }
+
+        filter.fquery._cache = trueFalse;
+        return this;
+      },
+  
+      /**
+            Sets the cache key.
+
+            @member ejs.QueryFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.fquery._cache_key;
+        }
+
+        filter.fquery._cache_key = key;
+        return this;
+      },
+            
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.QueryFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.QueryFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.QueryFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Matches documents with fields that have terms within a certain range.</p>
+
+    @name ejs.RangeFilter
+
+    @desc
+    Filters documents with fields that have terms within a certain range.
+
+    @param {String} field A valid field name.
+    */
+  ejs.RangeFilter = function (field) {
+
+    /**
+         The internal filter object. <code>Use get()</code>
+         @member ejs.RangeFilter
+         @property {Object} filter
+         */
+    var filter = {
+      range: {}
+    };
+
+    filter.range[field] = {};
+
+    return {
+
+      /**
+             The field to run the filter against.
+
+             @member ejs.RangeFilter
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = filter.range[field];
+
+        if (f == null) {
+          return field;
+        }
+
+        delete filter.range[field];
+        field = f;
+        filter.range[f] = oldValue;
+
+        return this;
+      },
+
+      /**
+            The lower bound. Defaults to start from the first.
+
+            @member ejs.RangeFilter
+            @param {Variable Type} f the lower bound value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      from: function (f) {
+        if (f == null) {
+          return filter.range[field].from;
+        }
+
+        filter.range[field].from = f;
+        return this;
+      },
+
+      /**
+            The upper bound. Defaults to unbounded.
+
+            @member ejs.RangeFilter
+            @param {Variable Type} t the upper bound value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      to: function (t) {
+        if (t == null) {
+          return filter.range[field].to;
+        }
+
+        filter.range[field].to = t;
+        return this;
+      },
+
+      /**
+            Should the first from (if set) be inclusive or not. 
+            Defaults to true
+
+            @member ejs.RangeFilter
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeLower: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.range[field].include_lower;
+        }
+
+        filter.range[field].include_lower = trueFalse;
+        return this;
+      },
+
+      /**
+            Should the last to (if set) be inclusive or not. Defaults to true.
+
+            @member ejs.RangeFilter
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeUpper: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.range[field].include_upper;
+        }
+
+        filter.range[field].include_upper = trueFalse;
+        return this;
+      },
+
+      /**
+            Greater than value.  Same as setting from to the value, and 
+            include_lower to false,
+
+            @member ejs.RangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gt: function (val) {
+        if (val == null) {
+          return filter.range[field].gt;
+        }
+
+        filter.range[field].gt = val;
+        return this;
+      },
+
+      /**
+            Greater than or equal to value.  Same as setting from to the value,
+            and include_lower to true.
+
+            @member ejs.RangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gte: function (val) {
+        if (val == null) {
+          return filter.range[field].gte;
+        }
+
+        filter.range[field].gte = val;
+        return this;
+      },
+
+      /**
+            Less than value.  Same as setting to to the value, and include_upper 
+            to false.
+
+            @member ejs.RangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lt: function (val) {
+        if (val == null) {
+          return filter.range[field].lt;
+        }
+
+        filter.range[field].lt = val;
+        return this;
+      },
+
+      /**
+            Less than or equal to value.  Same as setting to to the value, 
+            and include_upper to true.
+
+            @member ejs.RangeFilter
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lte: function (val) {
+        if (val == null) {
+          return filter.range[field].lte;
+        }
+
+        filter.range[field].lte = val;
+        return this;
+      },
+                          
+      /**
+            Sets the filter name.
+
+            @member ejs.RangeFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.range._name;
+        }
+
+        filter.range._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.RangeFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.range._cache;
+        }
+
+        filter.range._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.RangeFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.range._cache_key;
+        }
+
+        filter.range._cache_key = key;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.RangeFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.RangeFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.RangeFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filters documents that have a field value matching a regular expression. 
+    Based on Lucene 4.0 RegexpFilter which uses automaton to efficiently iterate 
+    over index terms.</p>
+
+    @name ejs.RegexpFilter
+
+    @desc
+    Matches documents that have fields matching a regular expression.
+
+    @param {String} field A valid field name.
+    @param {String} value A regex pattern.
+    */
+  ejs.RegexpFilter = function (field, value) {
+
+    /**
+         The internal filter object. <code>Use get()</code>
+         @member ejs.RegexpFilter
+         @property {Object} filter
+         */
+    var filter = {
+      regexp: {}
+    };
+
+    filter.regexp[field] = {
+      value: value
+    };
+
+    return {
+
+      /**
+             The field to run the filter against.
+
+             @member ejs.RegexpFilter
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = filter.regexp[field];
+
+        if (f == null) {
+          return field;
+        }
+
+        delete filter.regexp[field];
+        field = f;
+        filter.regexp[f] = oldValue;
+
+        return this;
+      },
+
+      /**
+            The regexp value.
+
+            @member ejs.RegexpFilter
+            @param {String} p A string regexp
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      value: function (p) {
+        if (p == null) {
+          return filter.regexp[field].value;
+        }
+
+        filter.regexp[field].value = p;
+        return this;
+      },
+
+      /**
+            The regex flags to use.  Valid flags are:
+        
+            INTERSECTION - Support for intersection notation
+            COMPLEMENT - Support for complement notation
+            EMPTY - Support for the empty language symbol: #
+            ANYSTRING - Support for the any string symbol: @
+            INTERVAL - Support for numerical interval notation: <n-m>
+            NONE - Disable support for all syntax options
+            ALL - Enables support for all syntax options
+        
+            Use multiple flags by separating with a "|" character.  Example:
+        
+            INTERSECTION|COMPLEMENT|EMPTY
+
+            @member ejs.RegexpFilter
+            @param {String} f The flags as a string, separate multiple flags with "|".
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      flags: function (f) {
+        if (f == null) {
+          return filter.regexp[field].flags;
+        }
+
+        filter.regexp[field].flags = f;
+        return this;
+      },
+  
+      /**
+            The regex flags to use as a numeric value.  Advanced use only,
+            it is probably better to stick with the <code>flags</code> option.
+        
+            @member ejs.RegexpFilter
+            @param {String} v The flags as a numeric value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      flagsValue: function (v) {
+        if (v == null) {
+          return filter.regexp[field].flags_value;
+        }
+
+        filter.regexp[field].flags_value = v;
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.RegexpFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.regexp._name;
+        }
+
+        filter.regexp._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.RegexpFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.regexp._cache;
+        }
+
+        filter.regexp._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.RegexpFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.regexp._cache_key;
+        }
+
+        filter.regexp._cache_key = key;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.RegexpFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+        
+            @member ejs.RegexpFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+  
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.RegexpFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A filter allowing to define scripts as filters</p>
+
+    @name ejs.ScriptFilter
+
+    @desc
+    A filter allowing to define scripts as filters.
+
+    @param {String} script The script as a string.
+    */
+  ejs.ScriptFilter = function (script) {
+
+    /**
+         The internal filter object. <code>Use get()</code>
+         @member ejs.ScriptFilter
+         @property {Object} filter
+         */
+    var filter = {
+      script: {
+        script: script
+      }
+    };
+
+    return {
+
+      /**
+            Sets the script.
+
+            @member ejs.ScriptFilter
+            @param {String} s The script as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      script: function (s) {
+        if (s == null) {
+          return filter.script.script;
+        }
+  
+        filter.script.script = s;
+        return this;
+      },
+
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.ScriptFilter
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return filter.script.params;
+        }
+    
+        filter.script.params = p;
+        return this;
+      },
+    
+      /**
+            Sets the script language.
+
+            @member ejs.ScriptFilter
+            @param {String} lang The script language, default mvel.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (lang) {
+        if (lang == null) {
+          return filter.script.lang;
+        }
+  
+        filter.script.lang = lang;
+        return this;
+      },
+    
+      /**
+            Sets the filter name.
+
+            @member ejs.ScriptFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.script._name;
+        }
+
+        filter.script._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.ScriptFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.script._cache;
+        }
+
+        filter.script._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.ScriptFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.script._cache_key;
+        }
+
+        filter.script._cache_key = key;
+        return this;
+      },
+             
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.ScriptFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.ScriptFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.ScriptFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Constructs a filter for docs matching any of the terms added to this
+    object. Unlike a RangeFilter this can be used for filtering on multiple
+    terms that are not necessarily in a sequence.</p>
+
+    @name ejs.TermFilter
+
+    @desc
+    Constructs a filter for docs matching the term added to this object.
+
+    @param {string} fieldName The document field/fieldName to execute the filter against.
+    @param {string} term The literal term used to filter the results.
+    */
+  ejs.TermFilter = function (fieldName, term) {
+
+    /**
+         The internal filter object. Use the get() method for access.
+         @member ejs.TermFilter
+         @property {Object} filter
+         */
+    var filter = {
+      term: {}
+    };
+
+    filter.term[fieldName] = term;
+
+    return {
+
+      /**
+             Provides access to the filter fieldName used to construct the 
+             termFilter object.
+             
+             @member ejs.TermFilter
+             @param {String} f the fieldName term
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+              When k is not specified, Returns {String}, the filter fieldName used to construct 
+              the termFilter object.
+             */
+      field: function (f) {
+        var oldValue = filter.term[fieldName];
+      
+        if (f == null) {
+          return fieldName;
+        }
+      
+        delete filter.term[fieldName];
+        fieldName = f;
+        filter.term[fieldName] = oldValue;
+      
+        return this;
+      },
+
+      /**
+             Provides access to the filter term used to construct the 
+             termFilter object.
+             
+             @member ejs.TermFilter
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+              When k is not specified, Returns {String}, the filter term used 
+              to construct the termFilter object.
+             */
+      term: function (v) {
+        if (v == null) {
+          return filter.term[fieldName];
+        }
+      
+        filter.term[fieldName] = v;
+        return this;
+      },
+
+      /**
+            Sets the filter name.
+
+            @member ejs.TermFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.term._name;
+        }
+
+        filter.term._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.TermFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.term._cache;
+        }
+
+        filter.term._cache = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the cache key.
+
+            @member ejs.TermFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.term._cache_key;
+        }
+
+        filter.term._cache_key = key;
+        return this;
+      },
+      
+      /**
+             Serializes the internal filter object as a JSON string.
+             
+             @member ejs.TermFilter
+             @returns {String} Returns a JSON representation of the termFilter object.
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+    
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TermFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Returns the filter object.  For internal use only.
+            
+            @member ejs.TermFilter
+            @returns {Object} Returns the object's filter property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filters documents that have fields that match any of the provided 
+    terms (not analyzed)</p>
+
+    @name ejs.TermsFilter
+
+    @desc
+    A Filter that matches documents containing provided terms. 
+
+    @param {String} field the document field/key to filter against
+    @param {String || Array} terms a single term or an array of terms.
+    */
+  ejs.TermsFilter = function (field, terms) {
+
+    /**
+         The internal filter object. <code>Use get()</code>
+         @member ejs.TermsFilter
+         @property {Object} filter
+         */
+    var filter = {
+      terms: {}
+    };
+   
+    if (isArray(terms)) {
+      filter.terms[field] = terms;
+    } else {
+      filter.terms[field] = [terms];
+    }
+
+    return {
+
+      /**
+            Sets the fields to filter against.
+
+            @member ejs.TermsFilter
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = filter.terms[field];
+    
+        if (f == null) {
+          return field;
+        }
+
+        delete filter.terms[field];
+        field = f;
+        filter.terms[f] = oldValue;
+    
+        return this;
+      },
+  
+      /**
+            Sets the terms.  If t is a String, it is added to the existing
+            list of terms.  If t is an array, the list of terms replaces the
+            existing terms.
+
+            @member ejs.TermsFilter
+            @param {String || Array} t A single term or an array or terms.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      terms: function (t) {
+        if (t == null) {
+          return filter.terms[field];
+        }
+
+        if (isArray(t)) {
+          filter.terms[field] = t;
+        } else {
+          filter.terms[field].push(t);
+        }
+    
+        return this;
+      },
+
+      /**
+            Sets the way terms filter executes is by iterating over the terms 
+            provided and finding matches docs (loading into a bitset) and 
+            caching it.  Valid values are: plain, bool, bool_nocache, and, 
+            and_nocache, or, or_nocache.  Defaults to plain.
+
+            @member ejs.TermsFilter
+            @param {String} e A valid execution method.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      execution: function (e) {
+        if (e == null) {
+          return filter.terms.execution;
+        }
+      
+        e = e.toLowerCase();
+        if (e === 'plain' || e === 'bool' || e === 'bool_nocache' || 
+          e === 'and' || e === 'and_nocache' || e === 'or' || e === 'or_nocache') {
+          filter.terms.execution = e;
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets the filter name.
+
+            @member ejs.TermsFilter
+            @param {String} name A name for the filter.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      name: function (name) {
+        if (name == null) {
+          return filter.terms._name;
+        }
+
+        filter.terms._name = name;
+        return this;
+      },
+
+      /**
+            Enable or disable caching of the filter
+
+            @member ejs.TermsFilter
+            @param {Boolean} trueFalse True to cache the filter, false otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return filter.terms._cache;
+        }
+
+        filter.terms._cache = trueFalse;
+        return this;
+      },
+  
+      /**
+            Sets the cache key.
+
+            @member ejs.TermsFilter
+            @param {String} key the cache key as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (key) {
+        if (key == null) {
+          return filter.terms._cache_key;
+        }
+
+        filter.terms._cache_key = key;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.TermsFilter
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TermsFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+            Retrieves the internal <code>filter</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.TermsFilter
+            @returns {String} returns this object's internal <code>filter</code> property.
+            */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A Filter that filters results by a specified index type.</p>
+
+    @name ejs.TypeFilter
+
+    @desc
+    Filter results by a specified index type.
+
+    @param {String} type the index type to filter on.
+    */
+  ejs.TypeFilter = function (type) {
+
+    /**
+         The internal filter object. Use <code>get()</code>
+
+         @member ejs.TypeFilter
+         @property {Object} filter
+         */
+    var filter = {
+      "type": {
+        "value": type
+      }
+    };
+
+    return {
+
+      /**
+             * Sets the type
+
+             @member ejs.TypeFilter
+             @param {String} type the index type to filter on
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      type: function (type) {
+        if (type == null) {
+          return filter.type.value;
+        }
+      
+        filter.type.value = type;
+        return this;
+      },
+
+      /**
+             Returns the filter container as a JSON string
+
+             @member ejs.TypeFilter
+             @returns {String} JSON representation of the notFilter object
+             */
+      toString: function () {
+        return JSON.stringify(filter);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TypeFilter
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'filter';
+      },
+      
+      /**
+             Returns the filter object.
+
+             @member ejs.TypeFilter
+             @returns {Object} filter object
+             */
+      _self: function () {
+        return filter;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The <code>Document</code> object provides an interface for working with
+    Documents.  Some example operations avaiable are storing documents,
+    retreiving documents, updating documents, and deleting documents from an
+    index.</p>
+
+    @name ejs.Document
+
+    @desc
+    Object used to create, replace, update, and delete documents
+
+    <div class="alert-message block-message info">
+        <p>
+            <strong>Tip: </strong>
+            It is not necessary to first create a index or content-type. If either of these
+            do not exist, they will be automatically created when you attempt to store the document.
+        </p>
+    </div>
+    
+    @param {String} index The index the document belongs to.
+    @param {String} type The type the document belongs to.
+    @param {String} id The id of the document.  The id is required except 
+      for indexing.  If no id is specified during indexing, one will be
+      created for you.
+      
+    */
+  ejs.Document = function (index, type, id) {
+
+    var params = {},
+    
+      // converts client params to a string param1=val1&param2=val1
+      genParamStr = function () {
+        var clientParams = genClientParams(),
+        parts = [];
+        
+        for (var p in clientParams) {
+          if (!has(clientParams, p)) {
+            continue;
+          }
+          
+          parts.push(p + '=' + encodeURIComponent(clientParams[p]));
+        }
+        
+        return parts.join('&');
+      },
+      
+      // Converts the stored params into parameters that will be passed
+      // to a client.  Certain parameter are skipped, and others require
+      // special processing before being sent to the client.
+      genClientParams = function () {
+        var clientParams = {};
+        
+        for (var param in params) {
+          if (!has(params, param)) {
+            continue;
+          }
+          
+          // skip params that don't go in the query string
+          if (param === 'upsert' || param === 'source' ||
+            param === 'script' || param === 'lang' || param === 'params') {
+            continue;
+          }
+                    
+          // process all over params
+          var paramVal = params[param];
+          if (isArray(paramVal)) {
+            paramVal = paramVal.join();
+          }
+            
+          clientParams[param] = paramVal;
+        }
+        
+        return clientParams;
+      };
+      
+    return {
+
+      /**
+             Sets the index the document belongs to.
+
+             @member ejs.Document
+             @param {String} idx The index name
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      index: function (idx) {
+        if (idx == null) {
+          return index;
+        }
+        
+        index = idx;
+        return this;
+      },
+      
+      /**
+             Sets the type of the document.
+
+             @member ejs.Document
+             @param {String} t The type name
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      type: function (t) {
+        if (t == null) {
+          return type;
+        }
+        
+        type = t;
+        return this;
+      },
+      
+      /**
+             Sets the id of the document.
+
+             @member ejs.Document
+             @param {String} i The document id
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      id: function (i) {
+        if (i == null) {
+          return id;
+        }
+        
+        id = i;
+        return this;
+      },
+      
+      /**
+             Sets the routing value. By default, the shard the document is
+             placed on is controlled by using a hash of the document’s id 
+             value. For more explicit control, this routing value will be fed 
+             into the hash function used by the router.
+             
+             This option is valid during the following operations:
+             index, delete, get, and update.
+
+             @member ejs.Document
+             @param {String} route The routing value
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      routing: function (route) {
+        if (route == null) {
+          return params.routing;
+        }
+        
+        params.routing = route;
+        return this;
+      },
+      
+      /**
+             Sets parent value for a child document.  When indexing a child 
+             document, the routing value is automatically set to be the same 
+             as it’s parent, unless the routing value is explicitly specified 
+             using the routing parameter.
+             
+             This option is valid during the following operations:
+             index, delete, get, and update.
+
+             @member ejs.Document
+             @param {String} parent The parent value
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      parent: function (parent) {
+        if (parent == null) {
+          return params.parent;
+        }
+        
+        params.parent = parent;
+        return this;
+      },
+      
+      /**
+             Sets timestamp of the document.  By default the timestamp will
+             be set to the time the docuement was indexed.
+             
+             This option is valid during the following operations:
+             index and update
+
+             @member ejs.Document
+             @param {String} parent The parent value
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      timestamp: function (ts) {
+        if (ts == null) {
+          return params.timestamp;
+        }
+        
+        params.timestamp = ts;
+        return this;
+      },
+      
+      /**
+             Sets the documents time to live (ttl).  The expiration date that 
+             will be set for a document with a provided ttl is relative to the 
+             timestamp of the document, meaning it can be based on the time of 
+             indexing or on any time provided. The provided ttl must be 
+             strictly positive and can be a number (in milliseconds) or any 
+             valid time value such as "1d", "2h", "5m", etc.
+             
+             This option is valid during the following operations:
+             index and update
+
+             @member ejs.Document
+             @param {String} length The amount of time after which the document
+              will expire.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      ttl: function (length) {
+        if (length == null) {
+          return params.ttl;
+        }
+        
+        params.ttl = length;
+        return this;
+      },
+      
+      /**
+             Set's a timeout for the given operation.  If the primary shard
+             has not completed the operation before this value, an error will
+             occur.  The default timeout is 1 minute. The provided timeout 
+             must be strictly positive and can be a number (in milliseconds) or 
+             any valid time value such as "1d", "2h", "5m", etc.
+             
+             This option is valid during the following operations:
+             index, delete, and update
+
+             @member ejs.Document
+             @param {String} length The amount of time after which the operation
+              will timeout.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      timeout: function (length) {
+        if (length == null) {
+          return params.timeout;
+        }
+        
+        params.timeout = length;
+        return this;
+      },
+      
+      /**
+             Enables the index to be refreshed immediately after the operation
+             occurs.  This is an advanced setting and can lead to performance
+             issues.
+             
+             This option is valid during the following operations:
+             index, delete, get, and update
+
+             @member ejs.Document
+             @param {Boolean} trueFalse If the index should be refreshed or not.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      refresh: function (trueFalse) {
+        if (trueFalse == null) {
+          return params.refresh;
+        }
+        
+        params.refresh = trueFalse;
+        return this;
+      },
+      
+      /**
+             Sets the document version.  Used for optimistic concurrency 
+             control when set.  If the version of the currently indexed
+             document is less-than or equal to the version specified, an
+             error is produced, otherwise the operation is permitted.
+             By default, internal versioning is used that starts at 1 and 
+             increments with each update. 
+             
+             This option is valid during the following operations:
+             index, delete, and update
+
+             @member ejs.Document
+             @param {Long} version A positive long value
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      version: function (version) {
+        if (version == null) {
+          return params.version;
+        }
+        
+        params.version = version;
+        return this;
+      },
+      
+      /**
+             Sets the version type.  Possible values are:
+             
+             internal - the default
+             external - to use your own version (ie. version number from a database)
+             
+             This option is valid during the following operations:
+             index, delete, and update
+
+             @member ejs.Document
+             @param {String} vt A version type (internal or external)
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      versionType: function (vt) {
+        // internal or external
+        if (vt == null) {
+          return params.version_type;
+        }
+        
+        vt = vt.toLowerCase();
+        if (vt === 'internal' || vt === 'external') {
+          params.version_type = vt;
+        }
+        
+        return this;
+      },
+      
+      /**
+             Perform percolation at index time.  Set to * to run document 
+             against all registered queries.  It is also possible to set this
+             value to a string in query string format, ie. "color:green".
+             
+             This option is valid during the following operations:
+             index and update
+
+             @member ejs.Document
+             @param {String} qry A percolation query string
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      percolate: function (qry) {
+        if (qry == null) {
+          return params.percolate;
+        }
+        
+        params.percolate = qry;
+        return this;
+      },
+      
+      /**
+             Sets the indexing operation type.  Valid values are:
+             
+             index - the default, create or replace
+             create - create only
+             
+             This option is valid during the following operations:
+             index
+
+             @member ejs.Document
+             @param {String} op The operation type (index or create)
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      opType: function (op) {
+        if (op == null) {
+          return params.op_type;
+        }
+        
+        op = op.toLowerCase();
+        if (op === 'index' || op === 'create') {
+          params.op_type = op;
+        }
+        
+        return this;
+      },
+      
+      /**
+             Sets the replication mode.  Valid values are:
+             
+             async - asynchronous replication to slaves
+             sync - synchronous replication to the slaves
+             default - the currently configured system default. 
+             
+             This option is valid during the following operations:
+             index, delete, and update
+
+             @member ejs.Document
+             @param {String} r The replication mode (async, sync, or default)
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      replication: function (r) {
+        if (r == null) {
+          return params.replication;
+        }
+        
+        r = r.toLowerCase();
+        if (r === 'async' || r === 'sync' || r === 'default') {
+          params.replication = r;
+        }
+        
+        return this;
+      },
+      
+      /**
+             Sets the write consistency.  Valid values are:
+             
+             one - only requires write to one shard
+             quorum - requires writes to quorum (N/2 + 1)
+             all - requires write to succeed on all shards
+             default - the currently configured system default
+             
+             This option is valid during the following operations:
+             index, delete, and update
+
+             @member ejs.Document
+             @param {String} c The write consistency (one, quorum, all, or default)
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      consistency: function (c) {
+        if (c == null) {
+          return params.consistency;
+        }
+        
+        c = c.toLowerCase();
+        if (c === 'default' || c === 'one' || c === 'quorum' || c === 'all') {
+          params.consistency = c;
+        }
+        
+        return this;
+      },
+      
+      /**
+             Sets the preference of which shard replicas to execute the get 
+             request on. By default, the operation is randomized between the 
+             shard replicas.  This value can be:
+             
+             _primary - execute only on the primary shard
+             _local - the local shard if possible
+             any string value - to guarentee the same shards will always be used
+             
+             This option is valid during the following operations:
+             get
+
+             @member ejs.Document
+             @param {String} p The preference value as a string
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      preference: function (p) {
+        if (p == null) {
+          return params.preference;
+        }
+        
+        params.preference = p;
+        return this;
+      },
+      
+      /**
+             Sets if the get request is performed in realtime or waits for
+             the indexing operations to complete.  By default it is realtime.
+             
+             This option is valid during the following operations:
+             get
+
+             @member ejs.Document
+             @param {Boolean} trueFalse If realtime get is used or not.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      realtime: function (trueFalse) {
+        if (trueFalse == null) {
+          return params.realtime;
+        }
+        
+        params.realtime = trueFalse;
+        return this;
+      },
+      
+      /**
+             Sets the fields of the document to return.  By default the 
+             _source field is returned.  Pass a single value to append to the
+             current list of fields, pass an array to overwrite the current
+             list of fields.  The returned fields will either be loaded if 
+             they are stored, or fetched from the _source
+             
+             This option is valid during the following operations:
+             get and update
+
+             @member ejs.Document
+             @param {String || Array} fields a single field name or array of field names.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      fields: function (fields) {
+        if (params.fields == null) {
+          params.fields = [];
+        }
+        
+        if (fields == null) {
+          return params.fields;
+        }
+        
+        if (isString(fields)) {
+          params.fields.push(fields);
+        } else if (isArray(fields)) {
+          params.fields = fields;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+        
+        return this;
+      },
+      
+      /**
+             Sets the update script.
+             
+             This option is valid during the following operations:
+             update
+
+             @member ejs.Document
+             @param {String} script a script to use for docuement updates
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      script: function (script) {
+        if (script == null) {
+          return params.script;
+        }
+        
+        params.script = script;
+        return this;
+      },
+      
+      /**
+             Sets the update script lanauge.  Defaults to mvel.
+             
+             This option is valid during the following operations:
+             update
+
+             @member ejs.Document
+             @param {String} lang a valid script lanauge type such as mvel.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      lang: function (lang) {
+        if (lang == null) {
+          return params.lang;
+        }
+        
+        params.lang = lang;
+        return this;
+      },
+      
+      /**
+             Sets the parameters sent to the update script.  The params must
+             be an object where the key is the parameter name and the value is
+             the parameter value to use in the script.  
+             
+             This option is valid during the following operations:
+             update
+
+             @member ejs.Document
+             @param {Object} p a object with script parameters.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      params: function (p) {
+        // accept object, prefix keys as sp_{key}
+        if (p == null) {
+          return params.params;
+        }
+        
+        if (!isObject(p)) {
+          throw new TypeError('Argument must be an object');
+        }
+        
+        params.params = p;
+        return this;
+      },
+      
+       /**
+               Sets how many times to retry if there is a version conflict 
+               between getting the document and indexing / deleting it. 
+               Defaults to 0.
+
+               This option is valid during the following operations:
+               update
+
+               @member ejs.Document
+               @param {Integer} num the number of times to retry operation.
+               @returns {Object} returns <code>this</code> so that calls can be chained.
+               */
+      retryOnConflict: function (num) {
+        if (num == null) {
+          return params.retry_on_conflict;
+        }
+        
+        params.retry_on_conflict = num;
+        return this;
+      },
+      
+      /**
+               Sets the upsert document.  The upsert document is used during
+               updates when the specified document you are attempting to 
+               update does not exist.
+
+               This option is valid during the following operations:
+               update
+
+               @member ejs.Document
+               @param {Object} doc the upset document.
+               @returns {Object} returns <code>this</code> so that calls can be chained.
+               */
+      upsert: function (doc) {
+        if (doc == null) {
+          return params.upsert;
+        }
+        
+        if (!isObject(doc)) {
+          throw new TypeError('Argument must be an object');
+        }
+        
+        params.upsert = doc;
+        return this;
+      },
+      
+      /**
+               Sets the source document.  When set during an update operation,
+               it is used as the partial update document.  
+
+               This option is valid during the following operations:
+               index and update
+
+               @member ejs.Document
+               @param {Object} doc the source document.
+               @returns {Object} returns <code>this</code> so that calls can be chained.
+               */
+      source: function (doc) {
+        if (doc == null) {
+          return params.source;
+        }
+        
+        if (!isObject(doc)) {
+          throw new TypeError('Argument must be an object');
+        }
+        
+        params.source = doc;
+        return this;
+      },
+      
+      /**
+            <p>Allows you to serialize this object into a JSON encoded string.</p>
+
+            @member ejs.Document
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(params);
+      },
+      
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.Document
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'document';
+      },
+      
+      /**
+            <p>Retrieves the internal <code>document</code> object. This is 
+            typically used by internal API functions so use with caution.</p>
+
+            @member ejs.Document
+            @returns {Object} returns this object's internal object.
+            */
+      _self: function () {
+        return params;
+      },
+      
+      /**
+            <p>Retrieves a document from the given index and type.</p>
+
+            @member ejs.Document
+            @param {Function} fnCallBack A callback function that handles the response.
+            @returns {Object} The return value is dependent on client implementation.
+            */
+      doGet: function (fnCallBack) {
+        // make sure the user has set a client
+        if (ejs.client == null) {
+          throw new Error("No Client Set");
+        }
+        
+        if (index == null || type == null || id == null) {
+          throw new Error('Index, Type, and ID must be set');
+        }
+        
+        // we don't need to convert the client params to a string
+        // on get requests, just create the url and pass the client
+        // params as the data
+        var url = '/' + index + '/' + type + '/' + id;
+        
+        return ejs.client.get(url, genClientParams(), fnCallBack);
+      },
+
+      /**
+            <p>Stores a document in the given index and type.  If no id 
+            is set, one is created during indexing.</p>
+
+            @member ejs.Document
+            @param {Function} fnCallBack A callback function that handles the response.
+            @returns {Object} The return value is dependent on client implementation.
+            */
+      doIndex: function (fnCallBack) {
+        // make sure the user has set a client
+        if (ejs.client == null) {
+          throw new Error("No Client Set");
+        }
+        
+        if (index == null || type == null) {
+          throw new Error('Index and Type must be set');
+        }
+        
+        if (params.source == null) {
+          throw new Error('No source document found');
+        }
+        
+        var url = '/' + index + '/' + type,
+          data = JSON.stringify(params.source),
+          paramStr = genParamStr(),
+          response;
+          
+        if (id != null) {
+          url = url + '/' + id;
+        }
+        
+        if (paramStr !== '') {
+          url = url + '?' + paramStr;
+        }
+        
+        // do post if id not set so one is created
+        if (id == null) {
+          response = ejs.client.post(url, data, fnCallBack);
+        } else {
+          // put when id is specified
+          response = ejs.client.put(url, data, fnCallBack);
+        }
+        
+        return response;
+      },
+
+      /**
+            <p>Updates a document in the given index and type.  If the 
+            document is not found in the index, the "upsert" value is used
+            if set.  The document is updated via an update script or partial
+            document.  To use a script, set the script option, to use a 
+            partial document, set the source with the partial document.</p>
+
+            @member ejs.Document
+            @param {Function} fnCallBack A callback function that handles the response.
+            @returns {Object} The return value is dependent on client implementation.
+            */
+      doUpdate: function (fnCallBack) {
+        // make sure the user has set a client
+        if (ejs.client == null) {
+          throw new Error("No Client Set");
+        }
+        
+        if (index == null || type == null || id == null) {
+          throw new Error('Index, Type, and ID must be set');
+        }
+        
+        if (params.script == null && params.source == null) {
+          throw new Error('Update script or document required');
+        }
+        
+        var url = '/' + index + '/' + type + '/' + id + '/_update',
+          data = {},
+          paramStr = genParamStr();
+        
+        if (paramStr !== '') {
+          url = url + '?' + paramStr;
+        }
+        
+        if (params.script != null) {
+          data.script = params.script;
+        }
+        
+        if (params.lang != null) {
+          data.lang = params.lang;
+        }
+        
+        if (params.params != null) {
+          data.params = params.params;
+        }
+        
+        if (params.upsert != null) {
+          data.upsert = params.upsert;
+        }
+        
+        if (params.source != null) {
+          data.doc = params.source;
+        }
+        
+        return ejs.client.post(url, JSON.stringify(data), fnCallBack);
+      },
+
+      /**
+            <p>Deletes the document from the given index and type using the 
+            speciifed id.</p>
+
+            @member ejs.Document
+            @param {Function} fnCallBack A callback function that handles the response.
+            @returns {void} Returns the value of the callback when executing on the server.
+            */
+      doDelete: function (fnCallBack) {
+        // make sure the user has set a client
+        if (ejs.client == null) {
+          throw new Error("No Client Set");
+        }
+        
+        if (index == null || type == null || id == null) {
+          throw new Error('Index, Type, and ID must be set');
+        }
+        
+        var url = '/' + index + '/' + type + '/' + id,
+          data = '',
+          paramStr = genParamStr();
+        
+        if (paramStr !== '') {
+          url = url + '?' + paramStr;
+        }
+        
+        return ejs.client.del(url, data, fnCallBack);
+      }
+
+    };
+  };
+
+
+  /**
+    @class
+    <p>A <code>boolQuery</code> allows you to build <em>Boolean</em> query constructs
+    from individual term or phrase queries. For example you might want to search
+    for documents containing the terms <code>javascript</code> and <code>python</code>.</p>
+
+    @name ejs.BoolQuery
+
+    @desc
+    A Query that matches documents matching boolean combinations of other
+    queries, e.g. <code>termQuerys, phraseQuerys</code> or other <code>boolQuerys</code>.
+
+    */
+  ejs.BoolQuery = function () {
+
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.BoolQuery
+         @property {Object} query
+         */
+    var query = {
+      bool: {}
+    };
+
+    return {
+
+      /**
+             Adds query to boolean container. Given query "must" appear in matching documents.
+
+             @member ejs.BoolQuery
+             @param {Object} oQuery A valid <code>Query</code> object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      must: function (oQuery) {
+        var i, len;
+        
+        if (query.bool.must == null) {
+          query.bool.must = [];
+        }
+    
+        if (oQuery == null) {
+          return query.bool.must;
+        }
+
+        if (isQuery(oQuery)) {
+          query.bool.must.push(oQuery._self());
+        } else if (isArray(oQuery)) {
+          query.bool.must = [];
+          for (i = 0, len = oQuery.length; i < len; i++) {
+            if (!isQuery(oQuery[i])) {
+              throw new TypeError('Argument must be an array of Queries');
+            }
+            
+            query.bool.must.push(oQuery[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Query or array of Queries');
+        }
+        
+        return this;
+      },
+
+      /**
+             Adds query to boolean container. Given query "must not" appear in matching documents.
+
+             @member ejs.BoolQuery
+             @param {Object} oQuery A valid query object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      mustNot: function (oQuery) {
+        var i, len;
+        
+        if (query.bool.must_not == null) {
+          query.bool.must_not = [];
+        }
+
+        if (oQuery == null) {
+          return query.bool.must_not;
+        }
+    
+        if (isQuery(oQuery)) {
+          query.bool.must_not.push(oQuery._self());
+        } else if (isArray(oQuery)) {
+          query.bool.must_not = [];
+          for (i = 0, len = oQuery.length; i < len; i++) {
+            if (!isQuery(oQuery[i])) {
+              throw new TypeError('Argument must be an array of Queries');
+            }
+            
+            query.bool.must_not.push(oQuery[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Query or array of Queries');
+        }
+        
+        return this;
+      },
+
+      /**
+             Adds query to boolean container. Given query "should" appear in matching documents.
+
+             @member ejs.BoolQuery
+             @param {Object} oQuery A valid query object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      should: function (oQuery) {
+        var i, len;
+        
+        if (query.bool.should == null) {
+          query.bool.should = [];
+        }
+
+        if (oQuery == null) {
+          return query.bool.should;
+        }
+    
+        if (isQuery(oQuery)) {
+          query.bool.should.push(oQuery._self());
+        } else if (isArray(oQuery)) {
+          query.bool.should = [];
+          for (i = 0, len = oQuery.length; i < len; i++) {
+            if (!isQuery(oQuery[i])) {
+              throw new TypeError('Argument must be an array of Queries');
+            }
+            
+            query.bool.should.push(oQuery[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Query or array of Queries');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.BoolQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.bool.boost;
+        }
+
+        query.bool.boost = boost;
+        return this;
+      },
+
+      /**
+            Enables or disables similarity coordinate scoring of documents
+            matching the <code>Query</code>. Default: false.
+
+            @member ejs.BoolQuery
+            @param {String} trueFalse A <code>true/false</code value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      disableCoord: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.bool.disable_coord;
+        }
+
+        query.bool.disable_coord = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the number of optional clauses that must match.
+      
+            By default no optional clauses are necessary for a match
+            (unless there are no required clauses).  If this method is used,
+            then the specified number of clauses is required..
+
+            Use of this method is totally independent of specifying that
+            any specific clauses are required (or prohibited).  This number will
+            only be compared against the number of matching optional clauses.
+   
+            @member ejs.BoolQuery
+            @param {Integer} minMatch A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minimumNumberShouldMatch: function (minMatch) {
+        if (minMatch == null) {
+          return query.bool.minimum_number_should_match;
+        }
+
+        query.bool.minimum_number_should_match = minMatch;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.BoolQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.BoolQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.BoolQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The boosting query can be used to effectively demote results that match 
+    a given query. Unlike the “NOT” clause in bool query, this still selects 
+    documents that contain undesirable terms, but reduces their overall 
+    score.</p>
+
+    @name ejs.BoostingQuery
+
+    @desc
+    <p>Constructs a query that can demote search results.  A negative boost.</p>
+
+    @param {Object} positiveQry Valid query object used to select all matching docs.
+    @param {Object} negativeQry Valid query object to match the undesirable docs 
+      returned within the positiveQry result set.
+    @param {Double} negativeBoost A double value where 0 < n < 1.
+     */
+  ejs.BoostingQuery = function (positiveQry, negativeQry, negativeBoost) {
+
+    if (!isQuery(positiveQry) || !isQuery(negativeQry)) {
+      throw new TypeError('Arguments must be Queries');
+    }
+    
+    /**
+         The internal Query object. Use <code>_self()</code>.
+         @member ejs.BoostingQuery
+         @property {Object} BoostingQuery
+         */
+    var query = {
+      boosting: {
+        positive: positiveQry._self(),
+        negative: negativeQry._self(),
+        negative_boost: negativeBoost
+      }
+    };
+
+    return {
+    
+      /**
+             Sets the "master" query that determines which results are returned.
+
+             @member ejs.BoostingQuery
+             @param {Object} oQuery A valid <code>Query</code> object
+             @returns {Object} returns <code>this</code> so that calls can be 
+              chained. Returns {Object} current positive query if oQuery is
+              not specified.
+             */
+      positive: function (oQuery) {
+        if (oQuery == null) {
+          return query.boosting.positive;
+        }
+    
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.boosting.positive = oQuery._self();
+        return this;
+      },
+
+      /**
+             Sets the query used to match documents in the <code>positive</code>
+             query that will be negatively boosted.
+
+             @member ejs.BoostingQuery
+             @param {Object} oQuery A valid <code>Query</code> object
+             @returns {Object} returns <code>this</code> so that calls can be 
+              chained. Returns {Object} current negative query if oQuery is
+              not specified.
+             */
+      negative: function (oQuery) {
+        if (oQuery == null) {
+          return query.boosting.negative;
+        }
+    
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.boosting.negative = oQuery._self();
+        return this;
+      },
+   
+      /**
+            Sets the negative boost value.
+
+            @member ejs.BoostingQuery
+            @param {Double} boost A positive <code>double</code> value where 0 < n < 1.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      negativeBoost: function (negBoost) {
+        if (negBoost == null) {
+          return query.boosting.negative_boost;
+        }
+
+        query.boosting.negative_boost = negBoost;
+        return this;
+      },
+    
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.BoostingQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.boosting.boost;
+        }
+
+        query.boosting.boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.BoostingQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.BoostingQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            
+            @member ejs.BoostingQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A constant score query wraps another <code>Query</code> or
+    <code>Filter</code> and returns a constant score for each
+    result that is equal to the query boost.</p>
+
+    <p>Note that lucene's query normalization (queryNorm) attempts
+    to make scores between different queries comparable.  It does not
+    change the relevance of your query, but it might confuse you when
+    you look at the score of your documents and they are not equal to
+    the query boost value as expected.  The scores were normalized by
+    queryNorm, but maintain the same relevance.</p>
+
+    @name ejs.ConstantScoreQuery
+
+    @desc
+    <p>Constructs a query where each documents returned by the internal
+    query or filter have a constant score equal to the boost factor.</p>
+
+     */
+  ejs.ConstantScoreQuery = function () {
+
+    /**
+         The internal Query object. Use <code>_self()</code>.
+         @member ejs.ConstantScoreQuery
+         @property {Object} query
+         */
+    var query = {
+      constant_score: {}
+    };
+
+    return {
+      /**
+             Adds the query to apply a constant score to.
+
+             @member ejs.ConstantScoreQuery
+             @param {Object} oQuery A valid <code>Query</code> object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      query: function (oQuery) {
+        if (oQuery == null) {
+          return query.constant_score.query;
+        }
+      
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.constant_score.query = oQuery._self();
+        return this;
+      },
+
+      /**
+             Adds the filter to apply a constant score to.
+
+             @member ejs.ConstantScoreQuery
+             @param {Object} oFilter A valid <code>Filter</code> object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filter: function (oFilter) {
+        if (oFilter == null) {
+          return query.constant_score.filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        query.constant_score.filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            Enables caching of the filter.
+
+            @member ejs.ConstantScoreQuery
+            @param {Boolean} trueFalse A boolean value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.constant_score._cache;
+        }
+
+        query.constant_score._cache = trueFalse;
+        return this;
+      },
+      
+      /**
+            Set the cache key.
+
+            @member ejs.ConstantScoreQuery
+            @param {String} k A string cache key.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (k) {
+        if (k == null) {
+          return query.constant_score._cache_key;
+        }
+
+        query.constant_score._cache_key = k;
+        return this;
+      },
+      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.ConstantScoreQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.constant_score.boost;
+        }
+
+        query.constant_score.boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.ConstantScoreQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.ConstantScoreQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            
+            @member ejs.ConstantScoreQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A query allows to wrap another query and multiply its score by the 
+    provided boost_factor. This can sometimes be desired since boost value set 
+    on specific queries gets normalized, while this query boost factor does not.</p>
+
+    @name ejs.CustomBoostFactorQuery
+
+    @desc
+    Boosts a queries score without that boost being normalized.
+
+    @param {Object} qry A valid query object.
+    */
+  ejs.CustomBoostFactorQuery = function (qry) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.CustomBoostFactorQuery
+         @property {Object} query
+         */
+    var query = {
+      custom_boost_factor: {
+        query: qry._self()
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query to be apply the custom boost to.
+
+            @member ejs.CustomBoostFactorQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.custom_boost_factor.query;
+        }
+    
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.custom_boost_factor.query = q._self();
+        return this;
+      },
+  
+      /**
+            Sets the language used in the script.  
+
+            @member ejs.CustomBoostFactorQuery
+            @param {Double} boost The boost value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boostFactor: function (boost) {
+        if (boost == null) {
+          return query.custom_boost_factor.boost_factor;
+        }
+
+        query.custom_boost_factor.boost_factor = boost;
+        return this;
+      },
+  
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.CustomBoostFactorQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.custom_boost_factor.boost;
+        }
+
+        query.custom_boost_factor.boost = boost;
+        return this;
+      },
+        
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.CustomBoostFactorQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.CustomBoostFactorQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.CustomBoostFactorQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A custom_filters_score query allows to execute a query, and if the hit 
+    matches a provided filter (ordered), use either a boost or a script 
+    associated with it to compute the score.</p>
+
+    <p>This can considerably simplify and increase performance for parameterized 
+    based scoring since filters are easily cached for faster performance, and 
+    boosting / script is considerably simpler.</p>
+  
+    @name ejs.CustomFiltersScoreQuery
+
+    @desc
+    Returned documents matched by the query and scored based on if the document
+    matched in a filter.  
+
+    @param {Object} qry A valid query object.
+    @param {Object || Array} filters A single object or array of objects.  Each 
+      object must have a 'filter' property and either a 'boost' or 'script' 
+      property.
+    */
+  ejs.CustomFiltersScoreQuery = function (qry, filters) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.CustomFiltersScoreQuery
+         @property {Object} query
+         */
+    var query = {
+      custom_filters_score: {
+        query: qry._self(),
+        filters: []
+      }
+    },
+  
+    // generate a valid filter object that can be inserted into the filters
+    // array.  Returns null when an invalid filter is passed in.
+    genFilterObject = function (filter) {
+      var obj = null;
+    
+      if (filter.filter && isFilter(filter.filter)) {
+        obj = {
+          filter: filter.filter._self()
+        };
+      
+        if (filter.boost) {
+          obj.boost = filter.boost;
+        } else if (filter.script) {
+          obj.script = filter.script;
+        } else {
+          // invalid filter, must boost or script must be specified
+          obj = null;
+        }
+      }
+    
+      return obj;
+    }; 
+
+    each((isArray(filters) ? filters : [filters]), function (filter) {
+      var fObj = genFilterObject(filter);
+      if (fObj !== null) {
+        query.custom_filters_score.filters.push(fObj);
+      }
+    });
+  
+    return {
+
+      /**
+            Sets the query to be apply the custom boost to.
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.custom_filters_score.query;
+        }
+  
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.custom_filters_score.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the filters and their related boost or script scoring method.
+            Takes an array of objects where each object has a 'filter' property
+            and either a 'boost' or 'script' property.  Pass a single object to
+            add to the current list of filters or pass a list of objects to
+            overwrite all existing filters.
+          
+            <code>
+            {filter: someFilter, boost: 2.1}
+            </code>
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {Object || Array} fltrs An object or array of objects 
+              contining a filter and either a boost or script property.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      filters: function (fltrs) {
+        if (fltrs == null) {
+          return query.custom_filters_score.filters;
+        }
+  
+        if (isArray(fltrs)) {
+          query.custom_filters_score.filters = [];
+        }
+        
+        each((isArray(fltrs) ? fltrs : [fltrs]), function (f) {
+          var fObj = genFilterObject(f);
+          if (fObj !== null) {
+            query.custom_filters_score.filters.push(fObj);
+          }
+        });
+      
+        return this;
+      },
+    
+      /**
+            A score_mode can be defined to control how multiple matching 
+            filters control the score. By default, it is set to first which 
+            means the first matching filter will control the score of the 
+            result. It can also be set to min/max/total/avg/multiply which 
+            will aggregate the result from all matching filters based on the 
+            aggregation type.
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {String} s The scoring type as a string. 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scoreMode: function (s) {
+        if (s == null) {
+          return query.custom_filters_score.score_mode;
+        }
+
+        s = s.toLowerCase();
+        if (s === 'first' || s === 'min' || s === 'max' || s === 'total' || s === 'avg' || s === 'multiply') {
+          query.custom_filters_score.score_mode = s;
+        }
+    
+        return this;
+      },
+    
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {Object} q An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return query.custom_filters_score.params;
+        }
+    
+        query.custom_filters_score.params = p;
+        return this;
+      },
+  
+      /**
+            Sets the language used in the script.  
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {String} l The script language, defatuls to mvel.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (l) {
+        if (l == null) {
+          return query.custom_filters_score.lang;
+        }
+
+        query.custom_filters_score.lang = l;
+        return this;
+      },
+
+      /**
+            Sets the maximum value a computed boost can reach.
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {Double} max A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxBoost: function (max) {
+        if (max == null) {
+          return query.custom_filters_score.max_boost;
+        }
+
+        query.custom_filters_score.max_boost = max;
+        return this;
+      },
+        
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.CustomFiltersScoreQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.custom_filters_score.boost;
+        }
+
+        query.custom_filters_score.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.CustomFiltersScoreQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.CustomFiltersScoreQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.CustomFiltersScoreQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A query that wraps another query and customize the scoring of it 
+    optionally with a computation derived from other field values in the 
+    doc (numeric ones) using script expression.</p>
+
+    @name ejs.CustomScoreQuery
+
+    @desc
+    Scores a query based on a script.
+
+    @param {Object} qry A valid query object.
+    @param {String} script A valid script expression.
+    */
+  ejs.CustomScoreQuery = function (qry, script) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.CustomScoreQuery
+         @property {Object} query
+         */
+    var query = {
+      custom_score: {
+        query: qry._self(),
+        script: script
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query to be apply the custom score to.
+
+            @member ejs.CustomScoreQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.custom_score.query;
+        }
+      
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.custom_score.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the script that calculates the custom score
+
+            @member ejs.CustomScoreQuery
+            @param {String} s A valid script expression
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      script: function (s) {
+        if (s == null) {
+          return query.custom_score.script;
+        }
+      
+        query.custom_score.script = s;
+        return this;
+      },
+
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            @member ejs.CustomScoreQuery
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return query.custom_score.params;
+        }
+      
+        query.custom_score.params = p;
+        return this;
+      },
+    
+      /**
+            Sets the language used in the script.  
+
+            @member ejs.CustomScoreQuery
+            @param {String} l The script language, defatuls to mvel.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (l) {
+        if (l == null) {
+          return query.custom_score.lang;
+        }
+
+        query.custom_score.lang = l;
+        return this;
+      },
+    
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.CustomScoreQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.custom_score.boost;
+        }
+
+        query.custom_score.boost = boost;
+        return this;
+      },
+          
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.CustomScoreQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.CustomScoreQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.CustomScoreQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    A query that generates the union of documents produced by its subqueries, and
+    that scores each document with the maximum score for that document as produced
+    by any subquery, plus a tie breaking increment for any additional matching
+    subqueries.
+
+    @name ejs.DisMaxQuery
+
+    @desc
+    A query that generates the union of documents produced by its subqueries such
+    as <code>termQuerys, phraseQuerys</code>, <code>boolQuerys</code>, etc.
+
+    */
+  ejs.DisMaxQuery = function () {
+
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.DisMaxQuery
+         @property {Object} query
+         */
+    var query = {
+      dis_max: {}
+    };
+
+    return {
+
+      /**
+            Updates the queries.  If passed a single Query, it is added to the
+            list of existing queries.  If passed an array of Queries, it 
+            replaces all existing values.
+
+            @member ejs.DisMaxQuery
+            @param {Query || Array} qs A single Query or an array of Queries
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      queries: function (qs) {
+        var i, len;
+        
+        if (qs == null) {
+          return query.dis_max.queries;
+        }
+      
+        if (query.dis_max.queries == null) {
+          query.dis_max.queries = [];
+        }
+        
+        if (isQuery(qs)) {
+          query.dis_max.queries.push(qs._self());
+        } else if (isArray(qs)) {
+          query.dis_max.queries = [];
+          for (i = 0, len = qs.length; i < len; i++) {
+            if (!isQuery(qs[i])) {
+              throw new TypeError('Argument must be array of Queries');
+            }
+            
+            query.dis_max.queries.push(qs[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be a Query or array of Queries');
+        }
+
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.  Default: 1.0.
+
+            @member ejs.DisMaxQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.dis_max.boost;
+        }
+
+        query.dis_max.boost = boost;
+        return this;
+      },
+
+
+      /**
+            The tie breaker value.  The tie breaker capability allows results
+            that include the same term in multiple fields to be judged better than
+            results that include this term in only the best of those multiple
+            fields, without confusing this with the better case of two different
+            terms in the multiple fields.  Default: 0.0.
+
+            @member ejs.DisMaxQuery
+            @param {Double} tieBreaker A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      tieBreaker: function (tieBreaker) {
+        if (tieBreaker == null) {
+          return query.dis_max.tie_breaker;
+        }
+
+        query.dis_max.tie_breaker = tieBreaker;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.DisMaxQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.DisMaxQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.DisMaxQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+  
+  /**
+    @class
+    <p>Wrapper to allow SpanQuery objects participate in composite single-field 
+    SpanQueries by 'lying' about their search field. That is, the masked 
+    SpanQuery will function as normal, but when asked for the field it 
+    queries against, it will return the value specified as the masked field vs.
+    the real field used in the wrapped span query.</p>
+
+    @name ejs.FieldMaskingSpanQuery
+
+    @desc
+    Wraps a SpanQuery and hides the real field being searched across.
+
+    @param {Query} spanQry A valid SpanQuery
+    @param {Integer} field the maximum field position in a match.
+  
+    */
+  ejs.FieldMaskingSpanQuery = function (spanQry, field) {
+
+    if (!isQuery(spanQry)) {
+      throw new TypeError('Argument must be a SpanQuery');
+    }
+  
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.FieldMaskingSpanQuery
+         @property {Object} query
+         */
+    var query = {
+      field_masking_span: {
+        query: spanQry._self(),
+        field: field
+      }
+    };
+
+    return {
+
+      /**
+            Sets the span query to wrap.
+
+            @member ejs.FieldMaskingSpanQuery
+            @param {Query} spanQuery Any valid span type query.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (spanQuery) {
+        if (spanQuery == null) {
+          return query.field_masking_span.query;
+        }
+    
+        if (!isQuery(spanQuery)) {
+          throw new TypeError('Argument must be a SpanQuery');
+        }
+      
+        query.field_masking_span.query = spanQuery._self();
+        return this;
+      },
+
+      /**
+            Sets the value of the "masked" field.  
+
+            @member ejs.FieldMaskingSpanQuery
+            @param {String} f A field name the wrapped span query should use
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        if (f == null) {
+          return query.field_masking_span.field;
+        }
+    
+        query.field_masking_span.field = f;
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.FieldMaskingSpanQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.field_masking_span.boost;
+        }
+
+        query.field_masking_span.boost = boost;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.FieldMaskingSpanQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FieldMaskingSpanQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.FieldMaskingSpanQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    A query that executes against a given field or document property. It is a simplified version
+    of the <code><a href="/jsdocs/ejs.queryString.html">queryString</a></code> object.
+
+    @name ejs.FieldQuery
+
+    @desc
+    A query that executes against a given field or document property.
+
+    @param {String} field The field or document property to search against.
+    @param {String} qstr The value to match.
+    */
+  ejs.FieldQuery = function (field, qstr) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.FieldQuery
+         @property {Object} query
+         */
+    var query = {
+      field: {}
+    };
+    
+    query.field[field] = {
+      query: qstr
+    };
+
+    return {
+
+      /**
+             The field to run the query against.
+
+             @member ejs.FieldQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.field[field];
+
+        if (f == null) {
+          return field;
+        }
+
+        delete query.field[field];
+        field = f;
+        query.field[f] = oldValue;
+
+        return this;
+      },
+      
+      /**
+             Sets the query string.
+
+             @member ejs.FieldQuery
+             @param {String} q The lucene query string.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      query: function (q) {
+        if (q == null) {
+          return query.field[field].query;
+        }
+
+        query.field[field].query = q;
+        return this;
+      },
+      
+      /**
+            Set the default <em>Boolean</em> operator. This operator is used 
+            to join individual query terms when no operator is explicity used 
+            in the query string (i.e., <code>this AND that</code>).
+            Defaults to <code>OR</code> (<em>same as Google</em>).
+
+            @member ejs.FieldQuery
+            @param {String} op The operator, AND or OR.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      defaultOperator: function (op) {
+        if (op == null) {
+          return query.field[field].default_operator;
+        }
+      
+        op = op.toUpperCase();
+        if (op === 'AND' || op === 'OR') {
+          query.field[field].default_operator = op;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the analyzer name used to analyze the <code>Query</code> object.
+
+            @member ejs.FieldQuery
+            @param {String} analyzer A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzer) {
+        if (analyzer == null) {
+          return query.field[field].analyzer;
+        }
+
+        query.field[field].analyzer = analyzer;
+        return this;
+      },
+
+      /**
+            Sets the quote analyzer name used to analyze the <code>query</code>
+            when in quoted text.
+
+            @member ejs.FieldQuery
+            @param {String} analyzer A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      quoteAnalyzer: function (analyzer) {
+        if (analyzer == null) {
+          return query.field[field].quote_analyzer;
+        }
+
+        query.field[field].quote_analyzer = analyzer;
+        return this;
+      },
+      
+      /**
+            Sets whether or not we should auto generate phrase queries *if* the
+            analyzer returns more than one term. Default: false.
+
+            @member ejs.FieldQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      autoGeneratePhraseQueries: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.field[field].auto_generate_phrase_queries;
+        }
+
+        query.field[field].auto_generate_phrase_queries = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not wildcard characters (* and ?) are allowed as the
+            first character of the <code>Query</code>.  Default: true.
+
+            @member ejs.FieldQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      allowLeadingWildcard: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.field[field].allow_leading_wildcard;
+        }
+
+        query.field[field].allow_leading_wildcard = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not terms from wildcard, prefix, fuzzy, and
+            range queries should automatically be lowercased in the <code>Query</code>
+            since they are not analyzed.  Default: true.
+
+            @member ejs.FieldQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lowercaseExpandedTerms: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.field[field].lowercase_expanded_terms;
+        }
+
+        query.field[field].lowercase_expanded_terms = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not position increments will be used in the
+            <code>Query</code>. Default: true.
+
+            @member ejs.FieldQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      enablePositionIncrements: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.field[field].enable_position_increments;
+        }
+
+        query.field[field].enable_position_increments = trueFalse;
+        return this;
+      },
+
+      /**
+            Set the minimum similarity for fuzzy queries.  Default: 0.5.
+
+            @member ejs.FieldQuery
+            @param {Double} minSim A <code>double</code> value between 0 and 1.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyMinSim: function (minSim) {
+        if (minSim == null) {
+          return query.field[field].fuzzy_min_sim;
+        }
+
+        query.field[field].fuzzy_min_sim = minSim;
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.  Default: 1.0.
+
+            @member ejs.FieldQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.field[field].boost;
+        }
+
+        query.field[field].boost = boost;
+        return this;
+      },
+
+      /**
+            Sets the prefix length for fuzzy queries.  Default: 0.
+
+            @member ejs.FieldQuery
+            @param {Integer} fuzzLen A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyPrefixLength: function (fuzzLen) {
+        if (fuzzLen == null) {
+          return query.field[field].fuzzy_prefix_length;
+        }
+
+        query.field[field].fuzzy_prefix_length = fuzzLen;
+        return this;
+      },
+
+      /**
+            Sets the max number of term expansions for fuzzy queries.  
+
+            @member ejs.FieldQuery
+            @param {Integer} max A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyMaxExpansions: function (max) {
+        if (max == null) {
+          return query.field[field].fuzzy_max_expansions;
+        }
+
+        query.field[field].fuzzy_max_expansions = max;
+        return this;
+      },
+
+      /**
+            Sets fuzzy rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+            
+            @member ejs.FieldQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyRewrite: function (m) {
+        if (m == null) {
+          return query.field[field].fuzzy_rewrite;
+        }
+
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.field[field].fuzzy_rewrite = m;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.FieldQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.field[field].rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.field[field].rewrite = m;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the suffix to automatically add to the field name when 
+            performing a quoted search.
+
+            @member ejs.FieldQuery
+            @param {String} s The suffix as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      quoteFieldSuffix: function (s) {
+        if (s == null) {
+          return query.field[field].quote_field_suffix;
+        }
+
+        query.field[field].quote_field_suffix = s;
+        return this;
+      },
+                        
+      /**
+            Sets the default slop for phrases. If zero, then exact phrase matches
+            are required.  Default: 0.
+
+            @member ejs.FieldQuery
+            @param {Integer} slop A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      phraseSlop: function (slop) {
+        if (slop == null) {
+          return query.field[field].phrase_slop;
+        }
+
+        query.field[field].phrase_slop = slop;
+        return this;
+      },
+
+      /**
+            Sets whether or not we should attempt to analyzed wilcard terms in the
+            <code>Query</code>. By default, wildcard terms are not analyzed.
+            Analysis of wildcard characters is not perfect.  Default: false.
+
+            @member ejs.FieldQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzeWildcard: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.field[field].analyze_wildcard;
+        }
+
+        query.field[field].analyze_wildcard = trueFalse;
+        return this;
+      },
+
+      /**
+            If they query string should be escaped or not.
+
+            @member ejs.FieldQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      escape: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.field[field].escape;
+        }
+
+        query.field[field].escape = trueFalse;
+        return this;
+      },
+      
+      /**
+            Sets a percent value controlling how many "should" clauses in the
+            resulting <code>Query</code> should match.
+
+            @member ejs.FieldQuery
+            @param {Integer} minMatch An <code>integer</code> between 0 and 100.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minimumShouldMatch: function (minMatch) {
+        if (minMatch == null) {
+          return query.field[field].minimum_should_match;
+        }
+
+        query.field[field].minimum_should_match = minMatch;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.FieldQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FieldQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.FieldQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filter queries allow you to restrict the results returned by a query. There are
+    several different types of filters that can be applied
+    (see <a href="/jsdocs/ejs.filter.html">filter</a> module). A <code>filterQuery</code>
+    takes a <code>Query</code> and a <code>Filter</code> object as arguments and constructs
+    a new <code>Query</code> that is then used for the search.</p>
+
+    @name ejs.FilteredQuery
+
+    @desc
+    <p>A query that applies a filter to the results of another query.</p>
+
+    @param {Object} someQuery a valid <code>Query</code> object
+    @param {Object} someFilter a valid <code>Filter</code> object.  This parameter
+      is optional.
+
+     */
+  ejs.FilteredQuery = function (someQuery, someFilter) {
+
+    if (!isQuery(someQuery)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    if (someFilter != null && !isFilter(someFilter)) {
+      throw new TypeError('Argument must be a Filter');
+    }
+    
+    /**
+         The internal query object. Use <code>_self()</code>
+         @member ejs.FilteredQuery
+         @property {Object} query
+         */
+    var query = {
+      filtered: {
+        query: someQuery._self()
+      }
+    };
+
+    if (someFilter != null) {
+      query.filtered.filter = someFilter._self();
+    }
+    
+    return {
+
+      /**
+             Adds the query to apply a constant score to.
+
+             @member ejs.FilteredQuery
+             @param {Object} oQuery A valid <code>Query</code> object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      query: function (oQuery) {
+        if (oQuery == null) {
+          return query.filtered.query;
+        }
+      
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.filtered.query = oQuery._self();
+        return this;
+      },
+
+      /**
+             Adds the filter to apply a constant score to.
+
+             @member ejs.FilteredQuery
+             @param {Object} oFilter A valid <code>Filter</code> object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filter: function (oFilter) {
+        if (oFilter == null) {
+          return query.filtered.filter;
+        }
+      
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        query.filtered.filter = oFilter._self();
+        return this;
+      },
+
+      /**
+            Sets the filter strategy.  The strategy defines how the filter is
+            applied during document collection.  Valid values are:
+            
+            query_filter - advance query scorer first then filter
+            random_access_random - random access filter
+            leap_frog - query scorer and filter "leap-frog", query goes first
+            leap_frog_filter_first - same as leap_frog, but filter goes first
+            random_access_N - replace N with integer, same as random access 
+              except you can specify a custom threshold
+
+            This is an advanced setting, use with care.
+            
+            @member ejs.FilteredQuery
+            @param {String} strategy The strategy as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      strategy: function (strategy) {
+        if (strategy == null) {
+          return query.filtered.strategy;
+        }
+
+        strategy = strategy.toLowerCase();
+        if (strategy === 'query_filter' || strategy === 'random_access_random' ||
+          strategy === 'leap_frog' || strategy === 'leap_frog_filter_first' ||
+          strategy.indexOf('random_access_') === 0) {
+            
+          query.filtered.strategy = strategy;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Enables caching of the filter.
+
+            @member ejs.FilteredQuery
+            @param {Boolean} trueFalse A boolean value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cache: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.filtered._cache;
+        }
+
+        query.filtered._cache = trueFalse;
+        return this;
+      },
+      
+      /**
+            Set the cache key.
+
+            @member ejs.FilteredQuery
+            @param {String} k A string cache key.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      cacheKey: function (k) {
+        if (k == null) {
+          return query.filtered._cache_key;
+        }
+
+        query.filtered._cache_key = k;
+        return this;
+      },
+      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.FilteredQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.filtered.boost;
+        }
+
+        query.filtered.boost = boost;
+        return this;
+      },
+      
+      /**
+             Converts this object to a json string
+             @member ejs.FilteredQuery
+             @returns {Object} string
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FilteredQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+             returns the query object.
+             @member ejs.FilteredQuery
+             @returns {Object} query object
+             */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The fuzzy_like_this_field query is the same as the fuzzy_like_this 
+    query, except that it runs against a single field. It provides nicer query 
+    DSL over the generic fuzzy_like_this query, and support typed fields 
+    query (automatically wraps typed fields with type filter to match only on 
+    the specific type).</p>
+
+    <p>Fuzzifies ALL terms provided as strings and then picks the best n 
+    differentiating terms. In effect this mixes the behaviour of FuzzyQuery and 
+    MoreLikeThis but with special consideration of fuzzy scoring factors. This 
+    generally produces good results for queries where users may provide details 
+    in a number of fields and have no knowledge of boolean query syntax and 
+    also want a degree of fuzzy matching and a fast query.</p>
+
+    <p>For each source term the fuzzy variants are held in a BooleanQuery with 
+    no coord factor (because we are not looking for matches on multiple variants 
+    in any one doc). Additionally, a specialized TermQuery is used for variants 
+    and does not use that variant term’s IDF because this would favour rarer 
+    terms eg misspellings. Instead, all variants use the same IDF 
+    ranking (the one for the source query term) and this is factored into the 
+    variant’s boost. If the source query term does not exist in the index the 
+    average IDF of the variants is used.</p>
+
+    @name ejs.FuzzyLikeThisFieldQuery
+
+    @desc
+    <p>Constructs a query where each documents returned are “like” provided text</p>
+
+    @param {String} field The field to run the query against.
+    @param {String} likeText The text to find documents like it.
+    */
+  ejs.FuzzyLikeThisFieldQuery = function (field, likeText) {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.FuzzyLikeThisFieldQuery
+         @property {Object} query
+         */
+    var query = {
+      flt_field: {}
+    };
+
+    query.flt_field[field] = {
+      like_text: likeText
+    };
+  
+    return {
+  
+      /**
+             The field to run the query against.
+
+             @member ejs.FuzzyLikeThisFieldQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.flt_field[field];
+      
+        if (f == null) {
+          return field;
+        }
+    
+        delete query.flt_field[field];
+        field = f;
+        query.flt_field[f] = oldValue;
+    
+        return this;
+      },
+  
+      /**
+            The text to find documents like
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {String} s A text string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      likeText: function (txt) {
+        if (txt == null) {
+          return query.flt_field[field].like_text;
+        }
+  
+        query.flt_field[field].like_text = txt;
+        return this;
+      },
+
+      /**
+            Should term frequency be ignored. Defaults to false.
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      ignoreTf: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.flt_field[field].ignore_tf;
+        }
+  
+        query.flt_field[field].ignore_tf = trueFalse;
+        return this;
+      },
+
+      /**
+            The maximum number of query terms that will be included in any 
+            generated query. Defaults to 25.
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxQueryTerms: function (max) {
+        if (max == null) {
+          return query.flt_field[field].max_query_terms;
+        }
+  
+        query.flt_field[field].max_query_terms = max;
+        return this;
+      },
+
+      /**
+            The minimum similarity of the term variants. Defaults to 0.5.
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {Double} min A positive double value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minSimilarity: function (min) {
+        if (min == null) {
+          return query.flt_field[field].min_similarity;
+        }
+  
+        query.flt_field[field].min_similarity = min;
+        return this;
+      },
+
+      /**
+            Length of required common prefix on variant terms. Defaults to 0..
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      prefixLength: function (len) {
+        if (len == null) {
+          return query.flt_field[field].prefix_length;
+        }
+  
+        query.flt_field[field].prefix_length = len;
+        return this;
+      },
+
+      /**
+            The analyzer that will be used to analyze the text. Defaults to the 
+            analyzer associated with the field.
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {String} analyzerName The name of the analyzer.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzerName) {
+        if (analyzerName == null) {
+          return query.flt_field[field].analyzer;
+        }
+  
+        query.flt_field[field].analyzer = analyzerName;
+        return this;
+      },
+                      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.FuzzyLikeThisFieldQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.flt_field[field].boost;
+        }
+
+        query.flt_field[field].boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.FuzzyLikeThisFieldQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FuzzyLikeThisFieldQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            @member ejs.FuzzyLikeThisFieldQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Fuzzy like this query find documents that are “like” provided text by 
+    running it against one or more fields.</p>
+
+    <p>Fuzzifies ALL terms provided as strings and then picks the best n 
+    differentiating terms. In effect this mixes the behaviour of FuzzyQuery and 
+    MoreLikeThis but with special consideration of fuzzy scoring factors. This 
+    generally produces good results for queries where users may provide details 
+    in a number of fields and have no knowledge of boolean query syntax and 
+    also want a degree of fuzzy matching and a fast query.</p>
+  
+    <p>For each source term the fuzzy variants are held in a BooleanQuery with 
+    no coord factor (because we are not looking for matches on multiple variants 
+    in any one doc). Additionally, a specialized TermQuery is used for variants 
+    and does not use that variant term’s IDF because this would favour rarer 
+    terms eg misspellings. Instead, all variants use the same IDF 
+    ranking (the one for the source query term) and this is factored into the 
+    variant’s boost. If the source query term does not exist in the index the 
+    average IDF of the variants is used.</p>
+
+    @name ejs.FuzzyLikeThisQuery
+
+    @desc
+    <p>Constructs a query where each documents returned are “like” provided text</p>
+
+    @param {String} likeText The text to find documents like it.
+    */
+  ejs.FuzzyLikeThisQuery = function (likeText) {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.FuzzyLikeThisQuery
+         @property {Object} query
+         */
+    var query = {
+      flt: {
+        like_text: likeText
+      }
+    };
+
+    return {
+    
+      /**
+             The fields to run the query against.  If you call with a single field,
+             it is added to the existing list of fields.  If called with an array
+             of field names, it replaces any existing values with the new array.
+
+             @member ejs.FuzzyLikeThisQuery
+             @param {String || Array} f A single field name or a list of field names.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      fields: function (f) {
+        if (query.flt.fields == null) {
+          query.flt.fields = [];
+        }
+      
+        if (f == null) {
+          return query.flt.fields;
+        }
+      
+        if (isString(f)) {
+          query.flt.fields.push(f);
+        } else if (isArray(f)) {
+          query.flt.fields = f;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+      
+        return this;
+      },
+    
+      /**
+            The text to find documents like
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {String} s A text string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      likeText: function (txt) {
+        if (txt == null) {
+          return query.flt.like_text;
+        }
+    
+        query.flt.like_text = txt;
+        return this;
+      },
+
+      /**
+            Should term frequency be ignored. Defaults to false.
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      ignoreTf: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.flt.ignore_tf;
+        }
+    
+        query.flt.ignore_tf = trueFalse;
+        return this;
+      },
+
+      /**
+            The maximum number of query terms that will be included in any 
+            generated query. Defaults to 25.
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxQueryTerms: function (max) {
+        if (max == null) {
+          return query.flt.max_query_terms;
+        }
+    
+        query.flt.max_query_terms = max;
+        return this;
+      },
+
+      /**
+            The minimum similarity of the term variants. Defaults to 0.5.
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {Double} min A positive double value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minSimilarity: function (min) {
+        if (min == null) {
+          return query.flt.min_similarity;
+        }
+    
+        query.flt.min_similarity = min;
+        return this;
+      },
+
+      /**
+            Length of required common prefix on variant terms. Defaults to 0..
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      prefixLength: function (len) {
+        if (len == null) {
+          return query.flt.prefix_length;
+        }
+    
+        query.flt.prefix_length = len;
+        return this;
+      },
+
+      /**
+            The analyzer that will be used to analyze the text. Defaults to the 
+            analyzer associated with the field.
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {String} analyzerName The name of the analyzer.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzerName) {
+        if (analyzerName == null) {
+          return query.flt.analyzer;
+        }
+    
+        query.flt.analyzer = analyzerName;
+        return this;
+      },
+                        
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.FuzzyLikeThisQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.flt.boost;
+        }
+
+        query.flt.boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.FuzzyLikeThisQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FuzzyLikeThisQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            @member ejs.FuzzyLikeThisQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A fuzzy search query based on the Damerau-Levenshtein (optimal string 
+    alignment) algorithm, though you can explicitly choose classic Levenshtein 
+    by passing false to the transpositions parameter./p>
+  
+    <p>fuzzy query on a numeric field will result in a range query “around” 
+    the value using the min_similarity value. As an example, if you perform a
+    fuzzy query against a field value of "12" with a min similarity setting
+    of "2", the query will search for values between "10" and "14".</p>
+
+    @name ejs.FuzzyQuery
+
+    @desc
+    <p>Constructs a query where each documents returned are “like” provided text</p>
+    
+    @param {String} field The field to run the fuzzy query against.
+    @param {String} value The value to fuzzify.
+    
+     */
+  ejs.FuzzyQuery = function (field, value) {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.FuzzyQuery
+         @property {Object} query
+         */
+    var query = {
+      fuzzy: {}
+    };
+
+    query.fuzzy[field] = {
+      value: value
+    };
+
+    return {
+
+      /**
+             The field to run the query against.
+
+             @member ejs.FuzzyQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.fuzzy[field];
+    
+        if (f == null) {
+          return field;
+        }
+  
+        delete query.fuzzy[field];
+        field = f;
+        query.fuzzy[f] = oldValue;
+  
+        return this;
+      },
+
+      /**
+            The query text to fuzzify.
+
+            @member ejs.FuzzyQuery
+            @param {String} s A text string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      value: function (txt) {
+        if (txt == null) {
+          return query.fuzzy[field].value;
+        }
+
+        query.fuzzy[field].value = txt;
+        return this;
+      },
+
+      /**
+            Set to false to use classic Levenshtein edit distance.
+
+            @member ejs.FuzzyQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      transpositions: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.fuzzy[field].transpositions;
+        }
+
+        query.fuzzy[field].transpositions = trueFalse;
+        return this;
+      },
+
+      /**
+            The maximum number of query terms that will be included in any 
+            generated query. Defaults to 50.
+
+            @member ejs.FuzzyQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxExpansions: function (max) {
+        if (max == null) {
+          return query.fuzzy[field].max_expansions;
+        }
+
+        query.fuzzy[field].max_expansions = max;
+        return this;
+      },
+
+      /**
+            The minimum similarity of the term variants. Defaults to 0.5.
+
+            @member ejs.FuzzyQuery
+            @param {Double} min A positive double value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minSimilarity: function (min) {
+        if (min == null) {
+          return query.fuzzy[field].min_similarity;
+        }
+
+        query.fuzzy[field].min_similarity = min;
+        return this;
+      },
+
+      /**
+            Length of required common prefix on variant terms. Defaults to 0..
+
+            @member ejs.FuzzyQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      prefixLength: function (len) {
+        if (len == null) {
+          return query.fuzzy[field].prefix_length;
+        }
+
+        query.fuzzy[field].prefix_length = len;
+        return this;
+      },
+      
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.FuzzyQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.fuzzy[field].rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.fuzzy[field].rewrite = m;
+        }
+        
+        return this;
+      },
+      
+                    
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.FuzzyQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.fuzzy[field].boost;
+        }
+
+        query.fuzzy[field].boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.FuzzyQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.FuzzyQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            @member ejs.FuzzyQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Efficient querying of documents containing shapes indexed using the 
+    geo_shape type.</p>
+
+    <p>Much like the geo_shape type, the geo_shape query uses a grid square 
+    representation of the query shape to find those documents which have shapes 
+    that relate to the query shape in a specified way. In order to do this, the 
+    field being queried must be of geo_shape type. The query will use the same 
+    PrefixTree configuration as defined for the field.</p>
+  
+    @name ejs.GeoShapeQuery
+
+    @desc
+    A Query to find documents with a geo_shapes matching a specific shape.
+
+    */
+  ejs.GeoShapeQuery = function (field) {
+
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.GeoShapeQuery
+         @property {Object} GeoShapeQuery
+         */
+    var query = {
+      geo_shape: {}
+    };
+
+    query.geo_shape[field] = {};
+
+    return {
+
+      /**
+            Sets the field to query against.
+
+            @member ejs.GeoShapeQuery
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = query.geo_shape[field];
+    
+        if (f == null) {
+          return field;
+        }
+
+        delete query.geo_shape[field];
+        field = f;
+        query.geo_shape[f] = oldValue;
+    
+        return this;
+      },
+
+      /**
+            Sets the shape
+
+            @member ejs.GeoShapeQuery
+            @param {String} shape A valid <code>Shape</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      shape: function (shape) {
+        if (shape == null) {
+          return query.geo_shape[field].shape;
+        }
+
+        if (query.geo_shape[field].indexed_shape != null) {
+          delete query.geo_shape[field].indexed_shape;
+        }
+        
+        query.geo_shape[field].shape = shape._self();
+        return this;
+      },
+
+      /**
+            Sets the indexed shape.  Use this if you already have shape definitions
+            already indexed.
+
+            @member ejs.GeoShapeQuery
+            @param {String} indexedShape A valid <code>IndexedShape</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      indexedShape: function (indexedShape) {
+        if (indexedShape == null) {
+          return query.geo_shape[field].indexed_shape;
+        }
+
+        if (query.geo_shape[field].shape != null) {
+          delete query.geo_shape[field].shape;
+        }
+        
+        query.geo_shape[field].indexed_shape = indexedShape._self();
+        return this;
+      },
+
+      /**
+            Sets the shape relation type.  A relationship between a Query Shape 
+            and indexed Shapes that will be used to determine if a Document 
+            should be matched or not.  Valid values are:  intersects, disjoint,
+            and within.
+
+            @member ejs.GeoShapeQuery
+            @param {String} indexedShape A valid <code>IndexedShape</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      relation: function (relation) {
+        if (relation == null) {
+          return query.geo_shape[field].relation;
+        }
+
+        relation = relation.toLowerCase();
+        if (relation === 'intersects' || relation === 'disjoint' || relation === 'within') {
+          query.geo_shape[field].relation = relation;
+        }
+      
+        return this;
+      },
+            
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.GeoShapeQuery
+            @param {Number} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.geo_shape[field].boost;
+        }
+
+        query.geo_shape[field].boost = boost;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.GeoShapeQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoShapeQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.GeoShapeQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The has_child query works the same as the has_child filter, 
+    by automatically wrapping the filter with a constant_score. Results in 
+    parent documents that have child docs matching the query being returned.</p>
+  
+    @name ejs.HasChildQuery
+
+    @desc
+    Returns results that have child documents matching the query.
+
+    @param {Object} qry A valid query object.
+    @param {String} type The child type
+    */
+  ejs.HasChildQuery = function (qry, type) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a valid Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.HasChildQuery
+         @property {Object} query
+         */
+    var query = {
+      has_child: {
+        query: qry._self(),
+        type: type
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query
+
+            @member ejs.HasChildQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.has_child.query;
+        }
+    
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a valid Query');
+        }
+        
+        query.has_child.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the child document type to search against
+
+            @member ejs.HasChildQuery
+            @param {String} t A valid type name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (t) {
+        if (t == null) {
+          return query.has_child.type;
+        }
+    
+        query.has_child.type = t;
+        return this;
+      },
+
+      /**
+            Sets the scope of the query.  A scope allows to run facets on the 
+            same scope name that will work against the child documents. 
+
+            @member ejs.HasChildQuery
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return query.has_child._scope;
+        }
+    
+        query.has_child._scope = s;
+        return this;
+      },
+
+      /**
+            Sets the scoring method.  Valid values are:
+            
+            none - the default, no scoring
+            max - the highest score of all matched child documents is used
+            sum - the sum the all the matched child documents is used
+            avg - the average of all matched child documents is used
+
+            @member ejs.HasChildQuery
+            @param {String} s The score type as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scoreType: function (s) {
+        if (s == null) {
+          return query.has_child.score_type;
+        }
+    
+        s = s.toLowerCase();
+        if (s === 'none' || s === 'max' || s === 'sum' || s === 'avg') {
+          query.has_child.score_type = s;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.HasChildQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.has_child.boost;
+        }
+
+        query.has_child.boost = boost;
+        return this;
+      },
+        
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.HasChildQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.HasChildQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.HasChildQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The has_parent query works the same as the has_parent filter, by 
+    automatically wrapping the filter with a constant_score. Results in 
+    child documents that have parent docs matching the query being returned.</p>
+
+    @name ejs.HasParentQuery
+
+    @desc
+    Returns results that have parent documents matching the query.
+
+    @param {Object} qry A valid query object.
+    @param {String} parentType The child type
+    */
+  ejs.HasParentQuery = function (qry, parentType) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.HasParentQuery
+         @property {Object} query
+         */
+    var query = {
+      has_parent: {
+        query: qry._self(),
+        parent_type: parentType
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query
+
+            @member ejs.HasParentQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.has_parent.query;
+        }
+  
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.has_parent.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the child document type to search against
+
+            @member ejs.HasParentQuery
+            @param {String} t A valid type name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      parentType: function (t) {
+        if (t == null) {
+          return query.has_parent.parent_type;
+        }
+  
+        query.has_parent.parent_type = t;
+        return this;
+      },
+
+      /**
+            Sets the scope of the query.  A scope allows to run facets on the 
+            same scope name that will work against the parent documents. 
+
+            @member ejs.HasParentQuery
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return query.has_parent._scope;
+        }
+  
+        query.has_parent._scope = s;
+        return this;
+      },
+
+      /**
+            Sets the scoring method.  Valid values are:
+            
+            none - the default, no scoring
+            score - the score of the parent is used in all child documents.
+
+            @member ejs.HasParentQuery
+            @param {String} s The score type as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scoreType: function (s) {
+        if (s == null) {
+          return query.has_parent.score_type;
+        }
+    
+        s = s.toLowerCase();
+        if (s === 'none' || s === 'score') {
+          query.has_parent.score_type = s;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.HasParentQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.has_parent.boost;
+        }
+
+        query.has_parent.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.HasParentQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.HasParentQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.HasParentQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Filters documents that only have the provided ids. Note, this filter 
+    does not require the _id field to be indexed since it works using the 
+    _uid field.</p>
+
+    @name ejs.IdsQuery
+
+    @desc
+    Matches documents with the specified id(s).
+
+    @param {Array || String} ids A single document id or a list of document ids.
+    */
+  ejs.IdsQuery = function (ids) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.IdsQuery
+         @property {Object} query
+         */
+    var query = {
+      ids: {}
+    };
+    
+    if (isString(ids)) {
+      query.ids.values = [ids];
+    } else if (isArray(ids)) {
+      query.ids.values = ids;
+    } else {
+      throw new TypeError('Argument must be string or array');
+    }
+
+    return {
+
+      /**
+            Sets the values array or adds a new value. if val is a string, it
+            is added to the list of existing document ids.  If val is an
+            array it is set as the document values and replaces any existing values.
+
+            @member ejs.IdsQuery
+            @param {Array || String} val An single document id or an array of document ids.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      values: function (val) {
+        if (val == null) {
+          return query.ids.values;
+        }
+    
+        if (isString(val)) {
+          query.ids.values.push(val);
+        } else if (isArray(val)) {
+          query.ids.values = val;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the type as a single type or an array of types.  If type is a
+            string, it is added to the list of existing types.  If type is an
+            array, it is set as the types and overwrites an existing types. This
+            parameter is optional.
+
+            @member ejs.IdsQuery
+            @param {Array || String} type A type or a list of types
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (type) {
+        if (query.ids.type == null) {
+          query.ids.type = [];
+        }
+        
+        if (type == null) {
+          return query.ids.type;
+        }
+        
+        if (isString(type)) {
+          query.ids.type.push(type);
+        } else if (isArray(type)) {
+          query.ids.type = type;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.IdsQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.ids.boost;
+        }
+
+        query.ids.boost = boost;
+        return this;
+      },
+            
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.IdsQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.IdsQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.IdsQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The indices query can be used when executed across multiple indices, 
+    allowing to have a query that executes only when executed on an index that 
+    matches a specific list of indices, and another query that executes when it 
+    is executed on an index that does not match the listed indices.</p>
+
+    @name ejs.IndicesQuery
+
+    @desc
+    A configurable query that is dependent on the index name.
+
+    @param {Object} qry A valid query object.
+    @param {String || Array} indices a single index name or an array of index 
+      names.
+    */
+  ejs.IndicesQuery = function (qry, indices) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.IndicesQuery
+         @property {Object} query
+         */
+    var query = {
+      indices: {
+        query: qry._self()
+      }
+    };
+
+    if (isString(indices)) {
+      query.indices.indices = [indices];
+    } else if (isArray(indices)) {
+      query.indices.indices = indices;
+    } else {
+      throw new TypeError('Argument must be a string or array');
+    }
+  
+    return {
+
+      /**
+            Sets the indicies the query should match.  When passed a string,
+            the index name is added to the current list of indices.  When passed
+            an array, it overwites all current indices.
+
+            @member ejs.IndicesQuery
+            @param {String || Array} i A single index name or an array of index names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      indices: function (i) {
+        if (i == null) {
+          return query.indices.indices;
+        }
+  
+        if (isString(i)) {
+          query.indices.indices.push(i);
+        } else if (isArray(i)) {
+          query.indices.indices = i;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+
+        return this;
+      },
+    
+      /**
+            Sets the query to be executed against the indices specified.
+
+            @member ejs.IndicesQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.indices.query;
+        }
+  
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.indices.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the query to be used on an index that does not match an index
+            name in the indices list.  Can also be set to "none" to not match any
+            documents or "all" to match all documents.
+
+            @member ejs.IndicesQuery
+            @param {Object || String} q A valid Query object or "none" or "all"
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      noMatchQuery: function (q) {
+        if (q == null) {
+          return query.indices.no_match_query;
+        }
+  
+        if (isString(q)) {
+          q = q.toLowerCase();
+          if (q === 'none' || q === 'all') {
+            query.indices.no_match_query = q;
+          }
+        } else if (isQuery(q)) {
+          query.indices.no_match_query = q._self();
+        } else {
+          throw new TypeError('Argument must be string or Query');
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.IndicesQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.indices.boost;
+        }
+
+        query.indices.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.IndicesQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.IndicesQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.IndicesQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>This query can be used to match all the documents
+    in a given set of collections and/or types.</p>
+
+    @name ejs.MatchAllQuery
+
+    @desc
+    <p>A query that returns all documents.</p>
+
+     */
+  ejs.MatchAllQuery = function () {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.MatchAllQuery
+         @property {Object} query
+         */
+    var query = {
+      match_all: {}
+    };
+
+    return {
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.MatchAllQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.match_all.boost;
+        }
+
+        query.match_all.boost = boost;
+        return this;
+      },
+      
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.MatchAllQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MatchAllQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            
+            @member ejs.MatchAllQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    A <code>MatchQuery</code> is a type of <code>Query</code> that accepts 
+    text/numerics/dates, analyzes it, generates a query based on the
+    <code>MatchQuery</code> type.
+  
+    @name ejs.MatchQuery
+
+    @desc
+    A Query that appects text, analyzes it, generates internal query based
+    on the MatchQuery type.
+
+    @param {String} field the document field/field to query against
+    @param {String} qstr the query string
+    */
+  ejs.MatchQuery = function (field, qstr) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.MatchQuery
+         @property {Object} query
+         */
+    var query = {
+      match: {}
+    };
+    
+    query.match[field] = {
+      query: qstr
+    };
+
+    return {
+
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.MatchQuery
+            @param {Number} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.match[field].boost;
+        }
+
+        query.match[field].boost = boost;
+        return this;
+      },
+
+      /**
+            Sets the query string for the <code>Query</code>.
+
+            @member ejs.MatchQuery
+            @param {String} qstr The query string to search for.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (qstr) {
+        if (qstr == null) {
+          return query.match[field].query;
+        }
+
+        query.match[field].query = qstr;
+        return this;
+      },
+
+      /**
+            Sets the type of the <code>MatchQuery</code>.  Valid values are
+            boolean, phrase, and phrase_prefix.
+
+            @member ejs.MatchQuery
+            @param {String} type Any of boolean, phrase, phrase_prefix.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (type) {
+        if (type == null) {
+          return query.match[field].type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'boolean' || type === 'phrase' || type === 'phrase_prefix') {
+          query.match[field].type = type;
+        }
+
+        return this;
+      },
+
+      /**
+            Sets the fuzziness value for the <code>Query</code>.
+
+            @member ejs.MatchQuery
+            @param {Double} fuzz A <code>double</code> value between 0.0 and 1.0.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzziness: function (fuzz) {
+        if (fuzz == null) {
+          return query.match[field].fuzziness;
+        }
+
+        query.match[field].fuzziness = fuzz;
+        return this;
+      },
+
+      /**
+            Sets the prefix length for a fuzzy prefix <code>MatchQuery</code>.
+
+            @member ejs.MatchQuery
+            @param {Integer} l A positive <code>integer</code> length value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      prefixLength: function (l) {
+        if (l == null) {
+          return query.match[field].prefix_length;
+        }
+
+        query.match[field].prefix_length = l;
+        return this;
+      },
+
+      /**
+            Sets the max expansions of a fuzzy <code>MatchQuery</code>.
+
+            @member ejs.MatchQuery
+            @param {Integer} e A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxExpansions: function (e) {
+        if (e == null) {
+          return query.match[field].max_expansions;
+        }
+
+        query.match[field].max_expansions = e;
+        return this;
+      },
+
+      /**
+            Sets default operator of the <code>Query</code>.  Default: or.
+
+            @member ejs.MatchQuery
+            @param {String} op Any of "and" or "or", no quote characters.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      operator: function (op) {
+        if (op == null) {
+          return query.match[field].operator;
+        }
+
+        op = op.toLowerCase();
+        if (op === 'and' || op === 'or') {
+          query.match[field].operator = op;
+        }
+
+        return this;
+      },
+
+      /**
+            Sets the default slop for phrases. If zero, then exact phrase matches
+            are required.  Default: 0.
+
+            @member ejs.MatchQuery
+            @param {Integer} slop A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      slop: function (slop) {
+        if (slop == null) {
+          return query.match[field].slop;
+        }
+
+        query.match[field].slop = slop;
+        return this;
+      },
+
+      /**
+            Sets the analyzer name used to analyze the <code>Query</code> object.
+
+            @member ejs.MatchQuery
+            @param {String} analyzer A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzer) {
+        if (analyzer == null) {
+          return query.match[field].analyzer;
+        }
+
+        query.match[field].analyzer = analyzer;
+        return this;
+      },
+
+      /**
+            Sets a percent value controlling how many "should" clauses in the
+            resulting <code>Query</code> should match.
+
+            @member ejs.MatchQuery
+            @param {Integer} minMatch An <code>integer</code> between 0 and 100.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minimumShouldMatch: function (minMatch) {
+        if (minMatch == null) {
+          return query.match[field].minimum_should_match;
+        }
+
+        query.match[field].minimum_should_match = minMatch;
+        return this;
+      },
+      
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.MatchQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.match[field].rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.match[field].rewrite = m;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets fuzzy rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+            
+            @member ejs.MatchQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyRewrite: function (m) {
+        if (m == null) {
+          return query.match[field].fuzzy_rewrite;
+        }
+
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.match[field].fuzzy_rewrite = m;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Set to false to use classic Levenshtein edit distance in the 
+            fuzzy query.
+
+            @member ejs.MatchQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyTranspositions: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.match[field].fuzzy_transpositions;
+        }
+
+        query.match[field].fuzzy_transpositions = trueFalse;
+        return this;
+      },
+
+      /**
+            Enables lenient parsing of the query string.
+
+            @member ejs.MatchQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lenient: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.match[field].lenient;
+        }
+
+        query.match[field].lenient = trueFalse;
+        return this;
+      },
+    
+      /**
+            Sets what happens when no terms match.  Valid values are
+            "all" or "none".  
+
+            @member ejs.MatchQuery
+            @param {String} q A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      zeroTermsQuery: function (q) {
+        if (q == null) {
+          return query.match[field].zero_terms_query;
+        }
+
+        q = q.toLowerCase();
+        if (q === 'all' || q === 'none') {
+          query.match[field].zero_terms_query = q;
+        }
+        
+        return this;
+      },
+              
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.MatchQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MatchQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.MatchQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The more_like_this_field query is the same as the more_like_this query, 
+    except it runs against a single field.</p>
+
+    @name ejs.MoreLikeThisFieldQuery
+
+    @desc
+    <p>Constructs a query where each documents returned are “like” provided text</p>
+
+    @param {String} field The field to run the query against.
+    @param {String} likeText The text to find documents like it.
+
+     */
+  ejs.MoreLikeThisFieldQuery = function (field, likeText) {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.MoreLikeThisFieldQuery
+         @property {Object} query
+         */
+    var query = {
+      mlt_field: {}
+    };
+
+    query.mlt_field[field] = {
+      like_text: likeText
+    };
+  
+    return {
+
+      /**
+             The field to run the query against.
+
+             @member ejs.MoreLikeThisFieldQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.mlt_field[field];
+    
+        if (f == null) {
+          return field;
+        }
+  
+        delete query.mlt_field[field];
+        field = f;
+        query.mlt_field[f] = oldValue;
+  
+        return this;
+      },
+
+      /**
+            The text to find documents like
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {String} s A text string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      likeText: function (txt) {
+        if (txt == null) {
+          return query.mlt_field[field].like_text;
+        }
+
+        query.mlt_field[field].like_text = txt;
+        return this;
+      },
+
+      /**
+            The percentage of terms to match on (float value). 
+            Defaults to 0.3 (30 percent).
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Double} percent A double value between 0 and 1.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      percentTermsToMatch: function (percent) {
+        if (percent == null) {
+          return query.mlt_field[field].percent_terms_to_match;
+        }
+
+        query.mlt_field[field].percent_terms_to_match = percent;
+        return this;
+      },
+
+      /**
+            The frequency below which terms will be ignored in the source doc. 
+            The default frequency is 2.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Integer} freq A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minTermFreq: function (freq) {
+        if (freq == null) {
+          return query.mlt_field[field].min_term_freq;
+        }
+
+        query.mlt_field[field].min_term_freq = freq;
+        return this;
+      },
+      
+      /**
+            The maximum number of query terms that will be included in any 
+            generated query. Defaults to 25.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxQueryTerms: function (max) {
+        if (max == null) {
+          return query.mlt_field[field].max_query_terms;
+        }
+
+        query.mlt_field[field].max_query_terms = max;
+        return this;
+      },
+
+      /**
+            An array of stop words. Any word in this set is considered 
+            “uninteresting” and ignored. Even if your Analyzer allows stopwords, 
+            you might want to tell the MoreLikeThis code to ignore them, as for 
+            the purposes of document similarity it seems reasonable to assume 
+            that “a stop word is never interesting”.
+        
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Array} stopWords An array of string stopwords
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      stopWords: function (stopWords) {
+        if (stopWords == null) {
+          return query.mlt_field[field].stop_words;
+        }
+
+        query.mlt_field[field].stop_words = stopWords;
+        return this;
+      },
+
+      /**
+            The frequency at which words will be ignored which do not occur in 
+            at least this many docs. Defaults to 5.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Integer} min A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minDocFreq: function (min) {
+        if (min == null) {
+          return query.mlt_field[field].min_doc_freq;
+        }
+
+        query.mlt_field[field].min_doc_freq = min;
+        return this;
+      },
+
+      /**
+            The maximum frequency in which words may still appear. Words that 
+            appear in more than this many docs will be ignored. 
+            Defaults to unbounded.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxDocFreq: function (max) {
+        if (max == null) {
+          return query.mlt_field[field].max_doc_freq;
+        }
+
+        query.mlt_field[field].max_doc_freq = max;
+        return this;
+      },
+
+      /**
+            The minimum word length below which words will be ignored. 
+            Defaults to 0.
+        
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minWordLen: function (len) {
+        if (len == null) {
+          return query.mlt_field[field].min_word_len;
+        }
+
+        query.mlt_field[field].min_word_len = len;
+        return this;
+      },
+
+      /**
+            The maximum word length above which words will be ignored. 
+            Defaults to unbounded (0).
+        
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxWordLen: function (len) {
+        if (len == null) {
+          return query.mlt_field[field].max_word_len;
+        }
+
+        query.mlt_field[field].max_word_len = len;
+        return this;
+      },
+          
+      /**
+            The analyzer that will be used to analyze the text. Defaults to the 
+            analyzer associated with the field.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {String} analyzerName The name of the analyzer.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzerName) {
+        if (analyzerName == null) {
+          return query.mlt_field[field].analyzer;
+        }
+
+        query.mlt_field[field].analyzer = analyzerName;
+        return this;
+      },
+  
+      /**
+            Sets the boost factor to use when boosting terms. 
+            Defaults to 1.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boostTerms: function (boost) {
+        if (boost == null) {
+          return query.mlt_field[field].boost_terms;
+        }
+
+        query.mlt_field[field].boost_terms = boost;
+        return this;
+      },
+                    
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.MoreLikeThisFieldQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.mlt_field[field].boost;
+        }
+
+        query.mlt_field[field].boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.MoreLikeThisFieldQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MoreLikeThisFieldQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            @member ejs.MoreLikeThisFieldQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>More like this query find documents that are “like” provided text by 
+    running it against one or more fields.</p>
+
+    @name ejs.MoreLikeThisQuery
+
+    @desc
+    <p>Constructs a query where each documents returned are “like” provided text</p>
+
+    @param {String || Array} fields A single field or array of fields to run against.
+    @param {String} likeText The text to find documents like it.
+  
+     */
+  ejs.MoreLikeThisQuery = function (fields, likeText) {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.MoreLikeThisQuery
+         @property {Object} query
+         */
+    var query = {
+      mlt: {
+        like_text: likeText,
+        fields: []
+      }
+    };
+
+    if (isString(fields)) {
+      query.mlt.fields.push(fields);
+    } else if (isArray(fields)) {
+      query.mlt.fields = fields;
+    } else {
+      throw new TypeError('Argument must be string or array');
+    }
+    
+    return {
+  
+      /**
+             The fields to run the query against.  If you call with a single field,
+             it is added to the existing list of fields.  If called with an array
+             of field names, it replaces any existing values with the new array.
+
+             @member ejs.MoreLikeThisQuery
+             @param {String || Array} f A single field name or a list of field names.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      fields: function (f) {
+        if (f == null) {
+          return query.mlt.fields;
+        }
+    
+        if (isString(f)) {
+          query.mlt.fields.push(f);
+        } else if (isArray(f)) {
+          query.mlt.fields = f;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+    
+        return this;
+      },
+  
+      /**
+            The text to find documents like
+
+            @member ejs.MoreLikeThisQuery
+            @param {String} s A text string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      likeText: function (txt) {
+        if (txt == null) {
+          return query.mlt.like_text;
+        }
+  
+        query.mlt.like_text = txt;
+        return this;
+      },
+
+      /**
+            The percentage of terms to match on (float value). 
+            Defaults to 0.3 (30 percent).
+
+            @member ejs.MoreLikeThisQuery
+            @param {Double} percent A double value between 0 and 1.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      percentTermsToMatch: function (percent) {
+        if (percent == null) {
+          return query.mlt.percent_terms_to_match;
+        }
+  
+        query.mlt.percent_terms_to_match = percent;
+        return this;
+      },
+
+      /**
+            The frequency below which terms will be ignored in the source doc. 
+            The default frequency is 2.
+
+            @member ejs.MoreLikeThisQuery
+            @param {Integer} freq A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minTermFreq: function (freq) {
+        if (freq == null) {
+          return query.mlt.min_term_freq;
+        }
+  
+        query.mlt.min_term_freq = freq;
+        return this;
+      },
+        
+      /**
+            The maximum number of query terms that will be included in any 
+            generated query. Defaults to 25.
+
+            @member ejs.MoreLikeThisQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxQueryTerms: function (max) {
+        if (max == null) {
+          return query.mlt.max_query_terms;
+        }
+  
+        query.mlt.max_query_terms = max;
+        return this;
+      },
+
+      /**
+            An array of stop words. Any word in this set is considered 
+            “uninteresting” and ignored. Even if your Analyzer allows stopwords, 
+            you might want to tell the MoreLikeThis code to ignore them, as for 
+            the purposes of document similarity it seems reasonable to assume 
+            that “a stop word is never interesting”.
+          
+            @member ejs.MoreLikeThisQuery
+            @param {Array} stopWords An array of string stopwords
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      stopWords: function (stopWords) {
+        if (stopWords == null) {
+          return query.mlt.stop_words;
+        }
+  
+        query.mlt.stop_words = stopWords;
+        return this;
+      },
+
+      /**
+            The frequency at which words will be ignored which do not occur in 
+            at least this many docs. Defaults to 5.
+
+            @member ejs.MoreLikeThisQuery
+            @param {Integer} min A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minDocFreq: function (min) {
+        if (min == null) {
+          return query.mlt.min_doc_freq;
+        }
+  
+        query.mlt.min_doc_freq = min;
+        return this;
+      },
+
+      /**
+            The maximum frequency in which words may still appear. Words that 
+            appear in more than this many docs will be ignored. 
+            Defaults to unbounded.
+
+            @member ejs.MoreLikeThisQuery
+            @param {Integer} max A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxDocFreq: function (max) {
+        if (max == null) {
+          return query.mlt.max_doc_freq;
+        }
+  
+        query.mlt.max_doc_freq = max;
+        return this;
+      },
+
+      /**
+            The minimum word length below which words will be ignored. 
+            Defaults to 0.
+          
+            @member ejs.MoreLikeThisQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minWordLen: function (len) {
+        if (len == null) {
+          return query.mlt.min_word_len;
+        }
+  
+        query.mlt.min_word_len = len;
+        return this;
+      },
+
+      /**
+            The maximum word length above which words will be ignored. 
+            Defaults to unbounded (0).
+          
+            @member ejs.MoreLikeThisQuery
+            @param {Integer} len A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxWordLen: function (len) {
+        if (len == null) {
+          return query.mlt.max_word_len;
+        }
+  
+        query.mlt.max_word_len = len;
+        return this;
+      },
+            
+      /**
+            The analyzer that will be used to analyze the text. Defaults to the 
+            analyzer associated with the field.
+
+            @member ejs.MoreLikeThisQuery
+            @param {String} analyzerName The name of the analyzer.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzerName) {
+        if (analyzerName == null) {
+          return query.mlt.analyzer;
+        }
+  
+        query.mlt.analyzer = analyzerName;
+        return this;
+      },
+    
+      /**
+            Sets the boost factor to use when boosting terms. 
+            Defaults to 1.
+
+            @member ejs.MoreLikeThisQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boostTerms: function (boost) {
+        if (boost == null) {
+          return query.mlt.boost_terms;
+        }
+
+        query.mlt.boost_terms = boost;
+        return this;
+      },
+                      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.MoreLikeThisQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.mlt.boost;
+        }
+
+        query.mlt.boost = boost;
+        return this;
+      },
+
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             @member ejs.MoreLikeThisQuery
+             @returns {String} Returns a JSON representation of the Query object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MoreLikeThisQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            @member ejs.MoreLikeThisQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    A <code>MultiMatchQuery</code> query builds further on top of the 
+    <code>MatchQuery</code> by allowing multiple fields to be specified. 
+    The idea here is to allow to more easily build a concise match type query 
+    over multiple fields instead of using a relatively more expressive query 
+    by using multiple match queries within a bool query.
+  
+    @name ejs.MultiMatchQuery
+
+    @desc
+    A Query that allow to more easily build a MatchQuery 
+    over multiple fields
+
+    @param {String || Array} fields the single field or array of fields to search across
+    @param {String} qstr the query string
+    */
+  ejs.MultiMatchQuery = function (fields, qstr) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.MultiMatchQuery
+         @property {Object} query
+         */
+    var query = {
+      multi_match: {
+        query: qstr,
+        fields: []
+      }
+    };
+
+    if (isString(fields)) {
+      query.multi_match.fields.push(fields);
+    } else if (isArray(fields)) {
+      query.multi_match.fields = fields;
+    } else {
+      throw new TypeError('Argument must be string or array');
+    }
+    
+    return {
+
+      /**
+            Sets the fields to search across.  If passed a single value it is
+            added to the existing list of fields.  If passed an array of 
+            values, they overwite all existing values.
+
+            @member ejs.MultiMatchQuery
+            @param {String || Array} f A single field or list of fields names to 
+              search across.
+            @returns {Object} returns <code>this</code> so that calls can be 
+              chained. Returns {Array} current value if `f` not specified.
+            */
+      fields: function (f) {
+        if (f == null) {
+          return query.multi_match.fields;
+        }
+
+        if (isString(f)) {
+          query.multi_match.fields.push(f);
+        } else if (isArray(f)) {
+          query.multi_match.fields = f;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets whether or not queries against multiple fields should be combined using Lucene's
+            <a href="http://lucene.apache.org/java/3_0_0/api/core/org/apache/lucene/search/DisjunctionMaxQuery.html">
+            DisjunctionMaxQuery</a>
+
+            @member ejs.MultiMatchQuery
+            @param {String} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      useDisMax: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.multi_match.use_dis_max;
+        }
+      
+        query.multi_match.use_dis_max = trueFalse;
+        return this;
+      },
+
+      /**
+            The tie breaker value.  The tie breaker capability allows results
+            that include the same term in multiple fields to be judged better than
+            results that include this term in only the best of those multiple
+            fields, without confusing this with the better case of two different
+            terms in the multiple fields.  Default: 0.0.
+
+            @member ejs.MultiMatchQuery
+            @param {Double} tieBreaker A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      tieBreaker: function (tieBreaker) {
+        if (tieBreaker == null) {
+          return query.multi_match.tie_breaker;
+        }
+
+        query.multi_match.tie_breaker = tieBreaker;
+        return this;
+      },
+
+      /**
+            Sets a percent value controlling how many "should" clauses in the
+            resulting <code>Query</code> should match.
+
+            @member ejs.MultiMatchQuery
+            @param {Integer} minMatch An <code>integer</code> between 0 and 100.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minimumShouldMatch: function (minMatch) {
+        if (minMatch == null) {
+          return query.multi_match.minimum_should_match;
+        }
+
+        query.multi_match.minimum_should_match = minMatch;
+        return this;
+      },
+      
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.MultiMatchQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.multi_match.rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.multi_match.rewrite = m;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets fuzzy rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+            
+            @member ejs.MultiMatchQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyRewrite: function (m) {
+        if (m == null) {
+          return query.multi_match.fuzzy_rewrite;
+        }
+
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.multi_match.fuzzy_rewrite = m;
+        }
+        
+        return this;
+      },
+
+      /**
+            Enables lenient parsing of the query string.
+
+            @member ejs.MultiMatchQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lenient: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.multi_match.lenient;
+        }
+
+        query.multi_match.lenient = trueFalse;
+        return this;
+      },
+                 
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.MultiMatchQuery
+            @param {Number} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.multi_match.boost;
+        }
+
+        query.multi_match.boost = boost;
+        return this;
+      },
+
+      /**
+            Sets the query string for the <code>Query</code>.
+
+            @member ejs.MultiMatchQuery
+            @param {String} qstr The query string to search for.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (qstr) {
+        if (qstr == null) {
+          return query.multi_match.query;
+        }
+
+        query.multi_match.query = qstr;
+        return this;
+      },
+
+      /**
+            Sets the type of the <code>MultiMatchQuery</code>.  Valid values are
+            boolean, phrase, and phrase_prefix or phrasePrefix.
+
+            @member ejs.MultiMatchQuery
+            @param {String} type Any of boolean, phrase, phrase_prefix or phrasePrefix.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (type) {
+        if (type == null) {
+          return query.multi_match.type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'boolean' || type === 'phrase' || type === 'phrase_prefix') {
+          query.multi_match.type = type;
+        }
+
+        return this;
+      },
+
+      /**
+            Sets the fuzziness value for the <code>Query</code>.
+
+            @member ejs.MultiMatchQuery
+            @param {Double} fuzz A <code>double</code> value between 0.0 and 1.0.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzziness: function (fuzz) {
+        if (fuzz == null) {
+          return query.multi_match.fuzziness;
+        }
+
+        query.multi_match.fuzziness = fuzz;
+        return this;
+      },
+
+      /**
+            Sets the prefix length for a fuzzy prefix <code>Query</code>.
+
+            @member ejs.MultiMatchQuery
+            @param {Integer} l A positive <code>integer</code> length value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      prefixLength: function (l) {
+        if (l == null) {
+          return query.multi_match.prefix_length;
+        }
+
+        query.multi_match.prefix_length = l;
+        return this;
+      },
+
+      /**
+            Sets the max expansions of a fuzzy <code>Query</code>.
+
+            @member ejs.MultiMatchQuery
+            @param {Integer} e A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      maxExpansions: function (e) {
+        if (e == null) {
+          return query.multi_match.max_expansions;
+        }
+
+        query.multi_match.max_expansions = e;
+        return this;
+      },
+
+      /**
+            Sets default operator of the <code>Query</code>.  Default: or.
+
+            @member ejs.MultiMatchQuery
+            @param {String} op Any of "and" or "or", no quote characters.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      operator: function (op) {
+        if (op == null) {
+          return query.multi_match.operator;
+        }
+
+        op = op.toLowerCase();
+        if (op === 'and' || op === 'or') {
+          query.multi_match.operator = op;
+        }
+
+        return this;
+      },
+
+      /**
+            Sets the default slop for phrases. If zero, then exact phrase matches
+            are required.  Default: 0.
+
+            @member ejs.MultiMatchQuery
+            @param {Integer} slop A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      slop: function (slop) {
+        if (slop == null) {
+          return query.multi_match.slop;
+        }
+
+        query.multi_match.slop = slop;
+        return this;
+      },
+
+      /**
+            Sets the analyzer name used to analyze the <code>Query</code> object.
+
+            @member ejs.MultiMatchQuery
+            @param {String} analyzer A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzer) {
+        if (analyzer == null) {
+          return query.multi_match.analyzer;
+        }
+
+        query.multi_match.analyzer = analyzer;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.MultiMatchQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.MultiMatchQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>Query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.MultiMatchQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Nested queries allow you to search against content within objects that are
+       embedded inside of other objects. It is similar to <code>XPath</code> expressions
+       in <code>XML</code> both conceptually and syntactically.</p>
+
+    <p>The query is executed against the nested objects / docs as if they were 
+    indexed as separate docs and resulting in the rootparent doc (or parent 
+    nested mapping).</p>
+    
+    @name ejs.NestedQuery
+
+    @desc
+    <p>Constructs a query that is capable of executing a search against objects
+       nested within a document.</p>
+
+    @param {String} path The nested object path.
+
+     */
+  ejs.NestedQuery = function (path) {
+
+    /**
+         The internal Query object. Use <code>_self()</code>.
+         
+         @member ejs.NestedQuery
+         @property {Object} query
+         */
+    var query = {
+      nested: {
+        path: path
+      }
+    };
+
+    return {
+      
+      /**
+             Sets the root context for the nested query.
+             
+             @member ejs.NestedQuery
+             @param {String} path The path defining the root context for the nested query.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      path: function (path) {
+        if (path == null) {
+          return query.nested.path;
+        }
+      
+        query.nested.path = path;
+        return this;
+      },
+
+      /**
+             Sets the nested query to be executed.
+             
+             @member ejs.NestedQuery
+             @param {Object} oQuery A valid Query object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      query: function (oQuery) {
+        if (oQuery == null) {
+          return query.nested.query;
+        }
+    
+        if (!isQuery(oQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.nested.query = oQuery._self();
+        return this;
+      },
+
+
+      /**
+             Sets the nested filter to be executed.
+             
+             @member ejs.NestedQuery
+             @param {Object} oFilter A valid Filter object
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      filter: function (oFilter) {
+        if (oFilter == null) {
+          return query.nested.filter;
+        }
+    
+        if (!isFilter(oFilter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        query.nested.filter = oFilter._self();
+        return this;
+      },
+
+      /**
+             Sets how the inner (nested) matches affect scoring on the parent document.
+             
+             @member ejs.NestedQuery
+             @param {String} mode The mode of scoring to be used for nested matches.
+                             Options are avg, total, max, none - defaults to avg
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      scoreMode: function (mode) {
+        if (mode == null) {
+          return query.nested.score_mode;
+        }
+      
+        mode = mode.toLowerCase();
+        if (mode === 'avg' || mode === 'total' || mode === 'max' || 
+          mode === 'none') {
+            
+          query.nested.score_mode = mode;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the scope of the query.  A scope allows to run facets on the 
+            same scope name that will work against the nested documents. 
+
+            @member ejs.NestedQuery
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return query.nested._scope;
+        }
+
+        query.nested._scope = s;
+        return this;
+      },
+      
+      /**
+            Sets the boost value of the nested <code>Query</code>.
+
+            @member ejs.NestedQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.nested.boost;
+        }
+
+        query.nested.boost = boost;
+        return this;
+      },
+      
+      /**
+             Serializes the internal <em>query</em> object as a JSON string.
+             
+             @member ejs.NestedQuery
+             @returns {String} Returns a JSON representation of the termFilter object.
+             */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.NestedQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            This method is used to retrieve the raw query object. It's designed
+            for internal use when composing and serializing queries.
+            
+            @member ejs.NestedQuery
+            @returns {Object} Returns the object's <em>query</em> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Matches documents that have fields containing terms with a specified 
+    prefix (not analyzed). The prefix query maps to Lucene PrefixQuery.</p>
+
+    @name ejs.PrefixQuery
+
+    @desc
+    Matches documents containing the specified un-analyzed prefix.
+
+    @param {String} field A valid field name.
+    @param {String} value A string prefix.
+    */
+  ejs.PrefixQuery = function (field, value) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.PrefixQuery
+         @property {Object} query
+         */
+    var query = {
+      prefix: {}
+    };
+
+    query.prefix[field] = {
+      value: value
+    };
+  
+    return {
+
+      /**
+             The field to run the query against.
+
+             @member ejs.PrefixQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.prefix[field];
+  
+        if (f == null) {
+          return field;
+        }
+
+        delete query.prefix[field];
+        field = f;
+        query.prefix[f] = oldValue;
+
+        return this;
+      },
+
+      /**
+            The prefix value.
+
+            @member ejs.PrefixQuery
+            @param {String} p A string prefix
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      value: function (p) {
+        if (p == null) {
+          return query.prefix[field].value;
+        }
+
+        query.prefix[field].value = p;
+        return this;
+      },
+
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.PrefixQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.prefix[field].rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.prefix[field].rewrite = m;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.PrefixQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.prefix[field].boost;
+        }
+
+        query.prefix[field].boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.PrefixQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.PrefixQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.PrefixQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A query that is parsed using Lucene's default query parser. Although Lucene provides the
+    ability to create your own queries through its API, it also provides a rich query language
+    through the Query Parser, a lexer which interprets a string into a Lucene Query.</p>
+
+    </p>See the Lucene <a href="http://lucene.apache.org/java/2_9_1/queryparsersyntax.html">Query Parser Syntax</a>
+    for more information.</p>
+
+    @name ejs.QueryStringQuery
+
+    @desc
+    A query that is parsed using Lucene's default query parser.
+
+    @param {String} qstr A valid Lucene query string.
+    */
+  ejs.QueryStringQuery = function (qstr) {
+
+    /**
+         The internal Query object. Use <code>get()</code>.
+         @member ejs.QueryStringQuery
+         @property {Object} query
+         */
+    var query = {
+      query_string: {}
+    };
+
+    query.query_string.query = qstr;
+
+    return {
+
+      /**
+            Sets the query string on this <code>Query</code> object.
+
+            @member ejs.QueryStringQuery
+            @param {String} qstr A valid Lucene query string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (qstr) {
+        if (qstr == null) {
+          return query.query_string.query;
+        }
+
+        query.query_string.query = qstr;
+        return this;
+      },
+
+      /**
+            Sets the default field/property this query should execute against.
+
+            @member ejs.QueryStringQuery
+            @param {String} fieldName The name of document field/property.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      defaultField: function (fieldName) {
+        if (fieldName == null) {
+          return query.query_string.default_field;
+        }
+      
+        query.query_string.default_field = fieldName;
+        return this;
+      },
+
+      /**
+            A set of fields/properties this query should execute against.  
+            Pass a single value to add to the existing list of fields and 
+            pass an array to overwrite all existing fields.  For each field, 
+            you can apply a field specific boost by appending a ^boost to the 
+            field name.  For example, title^10, to give the title field a
+            boost of 10.
+
+            @member ejs.QueryStringQuery
+            @param {Array} fieldNames A list of document fields/properties.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fields: function (fieldNames) {
+        if (query.query_string.fields == null) {
+          query.query_string.fields = [];
+        }
+        
+        if (fieldNames == null) {
+          return query.query_string.fields;
+        }
+      
+        if (isString(fieldNames)) {
+          query.query_string.fields.push(fieldNames);
+        } else if (isArray(fieldNames)) {
+          query.query_string.fields = fieldNames;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets whether or not queries against multiple fields should be combined using Lucene's
+            <a href="http://lucene.apache.org/java/3_0_0/api/core/org/apache/lucene/search/DisjunctionMaxQuery.html">
+            DisjunctionMaxQuery</a>
+
+            @member ejs.QueryStringQuery
+            @param {String} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      useDisMax: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.use_dis_max;
+        }
+      
+        query.query_string.use_dis_max = trueFalse;
+        return this;
+      },
+
+      /**
+            Set the default <em>Boolean</em> operator. This operator is used to join individual query
+            terms when no operator is explicity used in the query string (i.e., <code>this AND that</code>).
+            Defaults to <code>OR</code> (<em>same as Google</em>).
+
+            @member ejs.QueryStringQuery
+            @param {String} op The operator to use, AND or OR.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      defaultOperator: function (op) {
+        if (op == null) {
+          return query.query_string.default_operator;
+        }
+      
+        op = op.toUpperCase();
+        if (op === 'AND' || op === 'OR') {
+          query.query_string.default_operator = op;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the analyzer name used to analyze the <code>Query</code> object.
+
+            @member ejs.QueryStringQuery
+            @param {String} analyzer A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzer: function (analyzer) {
+        if (analyzer == null) {
+          return query.query_string.analyzer;
+        }
+
+        query.query_string.analyzer = analyzer;
+        return this;
+      },
+
+      /**
+            Sets the quote analyzer name used to analyze the <code>query</code>
+            when in quoted text.
+
+            @member ejs.QueryStringQuery
+            @param {String} analyzer A valid analyzer name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      quoteAnalyzer: function (analyzer) {
+        if (analyzer == null) {
+          return query.query_string.quote_analyzer;
+        }
+
+        query.query_string.quote_analyzer = analyzer;
+        return this;
+      },
+      
+      /**
+            Sets whether or not wildcard characters (* and ?) are allowed as the
+            first character of the <code>Query</code>.  Default: true.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      allowLeadingWildcard: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.allow_leading_wildcard;
+        }
+
+        query.query_string.allow_leading_wildcard = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not terms from wildcard, prefix, fuzzy, and
+            range queries should automatically be lowercased in the <code>Query</code>
+            since they are not analyzed.  Default: true.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lowercaseExpandedTerms: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.lowercase_expanded_terms;
+        }
+
+        query.query_string.lowercase_expanded_terms = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not position increments will be used in the
+            <code>Query</code>. Default: true.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      enablePositionIncrements: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.enable_position_increments;
+        }
+
+        query.query_string.enable_position_increments = trueFalse;
+        return this;
+      },
+
+
+      /**
+            Sets the prefix length for fuzzy queries.  Default: 0.
+
+            @member ejs.QueryStringQuery
+            @param {Integer} fuzzLen A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyPrefixLength: function (fuzzLen) {
+        if (fuzzLen == null) {
+          return query.query_string.fuzzy_prefix_length;
+        }
+
+        query.query_string.fuzzy_prefix_length = fuzzLen;
+        return this;
+      },
+
+      /**
+            Set the minimum similarity for fuzzy queries.  Default: 0.5.
+
+            @member ejs.QueryStringQuery
+            @param {Double} minSim A <code>double</code> value between 0 and 1.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyMinSim: function (minSim) {
+        if (minSim == null) {
+          return query.query_string.fuzzy_min_sim;
+        }
+
+        query.query_string.fuzzy_min_sim = minSim;
+        return this;
+      },
+
+      /**
+            Sets the default slop for phrases. If zero, then exact phrase matches
+            are required.  Default: 0.
+
+            @member ejs.QueryStringQuery
+            @param {Integer} slop A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      phraseSlop: function (slop) {
+        if (slop == null) {
+          return query.query_string.phrase_slop;
+        }
+
+        query.query_string.phrase_slop = slop;
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.  Default: 1.0.
+
+            @member ejs.QueryStringQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.query_string.boost;
+        }
+
+        query.query_string.boost = boost;
+        return this;
+      },
+
+      /**
+            Sets whether or not we should attempt to analyzed wilcard terms in the
+            <code>Query</code>. By default, wildcard terms are not analyzed.
+            Analysis of wildcard characters is not perfect.  Default: false.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      analyzeWildcard: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.analyze_wildcard;
+        }
+
+        query.query_string.analyze_wildcard = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not we should auto generate phrase queries *if* the
+            analyzer returns more than one term. Default: false.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      autoGeneratePhraseQueries: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.auto_generate_phrase_queries;
+        }
+
+        query.query_string.auto_generate_phrase_queries = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets a percent value controlling how many "should" clauses in the
+            resulting <code>Query</code> should match.
+
+            @member ejs.QueryStringQuery
+            @param {Integer} minMatch An <code>integer</code> between 0 and 100.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minimumShouldMatch: function (minMatch) {
+        if (minMatch == null) {
+          return query.query_string.minimum_should_match;
+        }
+
+        query.query_string.minimum_should_match = minMatch;
+        return this;
+      },
+
+      /**
+            Sets the tie breaker value for a <code>Query</code> using
+            <code>DisMax</code>.  The tie breaker capability allows results
+            that include the same term in multiple fields to be judged better than
+            results that include this term in only the best of those multiple
+            fields, without confusing this with the better case of two different
+            terms in the multiple fields.  Default: 0.0.
+
+            @member ejs.QueryStringQuery
+            @param {Double} tieBreaker A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      tieBreaker: function (tieBreaker) {
+        if (tieBreaker == null) {
+          return query.query_string.tie_breaker;
+        }
+
+        query.query_string.tie_breaker = tieBreaker;
+        return this;
+      },
+
+      /**
+            If they query string should be escaped or not.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A <code>true/false</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      escape: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.escape;
+        }
+
+        query.query_string.escape = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the max number of term expansions for fuzzy queries.  
+
+            @member ejs.QueryStringQuery
+            @param {Integer} max A positive <code>integer</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyMaxExpansions: function (max) {
+        if (max == null) {
+          return query.query_string.fuzzy_max_expansions;
+        }
+
+        query.query_string.fuzzy_max_expansions = max;
+        return this;
+      },
+
+      /**
+            Sets fuzzy rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+            
+            @member ejs.QueryStringQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fuzzyRewrite: function (m) {
+        if (m == null) {
+          return query.query_string.fuzzy_rewrite;
+        }
+
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.query_string.fuzzy_rewrite = m;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.QueryStringQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.query_string.rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.query_string.rewrite = m;
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the suffix to automatically add to the field name when 
+            performing a quoted search.
+
+            @member ejs.QueryStringQuery
+            @param {String} s The suffix as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      quoteFieldSuffix: function (s) {
+        if (s == null) {
+          return query.query_string.quote_field_suffix;
+        }
+
+        query.query_string.quote_field_suffix = s;
+        return this;
+      },
+      
+      /**
+            Enables lenient parsing of the query string.
+
+            @member ejs.QueryStringQuery
+            @param {Boolean} trueFalse A boolean value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lenient: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.query_string.lenient;
+        }
+
+        query.query_string.lenient = trueFalse;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.QueryStringQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.QueryStringQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.QueryStringQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Matches documents with fields that have terms within a certain range. 
+    The type of the Lucene query depends on the field type, for string fields, 
+    the TermRangeQuery, while for number/date fields, the query is a 
+    NumericRangeQuery.</p>
+
+    @name ejs.RangeQuery
+
+    @desc
+    Matches documents with fields that have terms within a certain range.
+
+    @param {String} field A valid field name.
+    */
+  ejs.RangeQuery = function (field) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.RangeQuery
+         @property {Object} query
+         */
+    var query = {
+      range: {}
+    };
+
+    query.range[field] = {};
+
+    return {
+
+      /**
+             The field to run the query against.
+
+             @member ejs.RangeQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.range[field];
+
+        if (f == null) {
+          return field;
+        }
+
+        delete query.range[field];
+        field = f;
+        query.range[f] = oldValue;
+
+        return this;
+      },
+
+      /**
+            The lower bound. Defaults to start from the first.
+
+            @member ejs.RangeQuery
+            @param {Variable Type} f the lower bound value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      from: function (f) {
+        if (f == null) {
+          return query.range[field].from;
+        }
+
+        query.range[field].from = f;
+        return this;
+      },
+
+      /**
+            The upper bound. Defaults to unbounded.
+
+            @member ejs.RangeQuery
+            @param {Variable Type} t the upper bound value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      to: function (t) {
+        if (t == null) {
+          return query.range[field].to;
+        }
+
+        query.range[field].to = t;
+        return this;
+      },
+
+      /**
+            Should the first from (if set) be inclusive or not. 
+            Defaults to true
+
+            @member ejs.RangeQuery
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeLower: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.range[field].include_lower;
+        }
+
+        query.range[field].include_lower = trueFalse;
+        return this;
+      },
+
+      /**
+            Should the last to (if set) be inclusive or not. Defaults to true.
+
+            @member ejs.RangeQuery
+            @param {Boolean} trueFalse true to include, false to exclude 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      includeUpper: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.range[field].include_upper;
+        }
+
+        query.range[field].include_upper = trueFalse;
+        return this;
+      },
+
+      /**
+            Greater than value.  Same as setting from to the value, and 
+            include_lower to false,
+
+            @member ejs.RangeQuery
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gt: function (val) {
+        if (val == null) {
+          return query.range[field].gt;
+        }
+
+        query.range[field].gt = val;
+        return this;
+      },
+
+      /**
+            Greater than or equal to value.  Same as setting from to the value,
+            and include_lower to true.
+
+            @member ejs.RangeQuery
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      gte: function (val) {
+        if (val == null) {
+          return query.range[field].gte;
+        }
+
+        query.range[field].gte = val;
+        return this;
+      },
+
+      /**
+            Less than value.  Same as setting to to the value, and include_upper 
+            to false.
+
+            @member ejs.RangeQuery
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lt: function (val) {
+        if (val == null) {
+          return query.range[field].lt;
+        }
+
+        query.range[field].lt = val;
+        return this;
+      },
+
+      /**
+            Less than or equal to value.  Same as setting to to the value, 
+            and include_upper to true.
+
+            @member ejs.RangeQuery
+            @param {Variable Type} val the value, type depends on field type
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lte: function (val) {
+        if (val == null) {
+          return query.range[field].lte;
+        }
+
+        query.range[field].lte = val;
+        return this;
+      },
+                            
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.RangeQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.range[field].boost;
+        }
+
+        query.range[field].boost = boost;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.RangeQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.RangeQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.RangeQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Matches documents that have fields matching a regular expression. Based 
+    on Lucene 4.0 RegexpQuery which uses automaton to efficiently iterate over 
+    index terms.</p>
+
+    @name ejs.RegexpQuery
+
+    @desc
+    Matches documents that have fields matching a regular expression.
+
+    @param {String} field A valid field name.
+    @param {String} value A regex pattern.
+    */
+  ejs.RegexpQuery = function (field, value) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.RegexpQuery
+         @property {Object} query
+         */
+    var query = {
+      regexp: {}
+    };
+
+    query.regexp[field] = {
+      value: value
+    };
+
+    return {
+
+      /**
+             The field to run the query against.
+
+             @member ejs.RegexpQuery
+             @param {String} f A single field name.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      field: function (f) {
+        var oldValue = query.regexp[field];
+
+        if (f == null) {
+          return field;
+        }
+
+        delete query.regexp[field];
+        field = f;
+        query.regexp[f] = oldValue;
+
+        return this;
+      },
+
+      /**
+            The regexp value.
+
+            @member ejs.RegexpQuery
+            @param {String} p A string regexp
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      value: function (p) {
+        if (p == null) {
+          return query.regexp[field].value;
+        }
+
+        query.regexp[field].value = p;
+        return this;
+      },
+
+      /**
+            The regex flags to use.  Valid flags are:
+          
+            INTERSECTION - Support for intersection notation
+            COMPLEMENT - Support for complement notation
+            EMPTY - Support for the empty language symbol: #
+            ANYSTRING - Support for the any string symbol: @
+            INTERVAL - Support for numerical interval notation: <n-m>
+            NONE - Disable support for all syntax options
+            ALL - Enables support for all syntax options
+          
+            Use multiple flags by separating with a "|" character.  Example:
+          
+            INTERSECTION|COMPLEMENT|EMPTY
+
+            @member ejs.RegexpQuery
+            @param {String} f The flags as a string, separate multiple flags with "|".
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      flags: function (f) {
+        if (f == null) {
+          return query.regexp[field].flags;
+        }
+
+        query.regexp[field].flags = f;
+        return this;
+      },
+    
+      /**
+            The regex flags to use as a numeric value.  Advanced use only,
+            it is probably better to stick with the <code>flags</code> option.
+          
+            @member ejs.RegexpQuery
+            @param {String} v The flags as a numeric value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      flagsValue: function (v) {
+        if (v == null) {
+          return query.regexp[field].flags_value;
+        }
+
+        query.regexp[field].flags_value = v;
+        return this;
+      },
+    
+      /**
+            Sets rewrite method.  Valid values are: 
+          
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+            
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+            
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+            
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+            
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+            
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+          
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.RegexpQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.regexp[field].rewrite;
+        }
+      
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+          
+          query.regexp[field].rewrite = m;
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.RegexpQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.regexp[field].boost;
+        }
+
+        query.regexp[field].boost = boost;
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.RegexpQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+          
+            @member ejs.RegexpQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+    
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.RegexpQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Matches spans near the beginning of a field. The spanFirstQuery allows you to search
+    for Spans that start and end within the first <code>n</code> positions of the document.
+    The span first query maps to Lucene SpanFirstQuery.</p>
+
+    @name ejs.SpanFirstQuery
+
+    @desc
+    Matches spans near the beginning of a field.
+
+    @param {Query} spanQry A valid SpanQuery
+    @param {Integer} end the maximum end position in a match.
+    
+    */
+  ejs.SpanFirstQuery = function (spanQry, end) {
+
+    if (!isQuery(spanQry)) {
+      throw new TypeError('Argument must be a SpanQuery');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.SpanFirstQuery
+         @property {Object} query
+         */
+    var query = {
+      span_first: {
+        match: spanQry._self(),
+        end: end
+      }
+    };
+
+    return {
+
+      /**
+            Sets the span query to match on.
+
+            @member ejs.SpanFirstQuery
+            @param {Object} spanQuery Any valid span type query.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      match: function (spanQuery) {
+        if (spanQuery == null) {
+          return query.span_first.match;
+        }
+      
+        if (!isQuery(spanQuery)) {
+          throw new TypeError('Argument must be a SpanQuery');
+        }
+        
+        query.span_first.match = spanQuery._self();
+        return this;
+      },
+
+      /**
+            Sets the maximum end position permitted in a match.
+
+            @member ejs.SpanFirstQuery
+            @param {Number} position The maximum position length to consider.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      end: function (position) {
+        if (position == null) {
+          return query.span_first.end;
+        }
+      
+        query.span_first.end = position;
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.SpanFirstQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.span_first.boost;
+        }
+
+        query.span_first.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.SpanFirstQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.SpanFirstQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.SpanFirstQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A spanNearQuery will look to find a number of spanQuerys within a given
+    distance from each other.</p>
+
+    @name ejs.SpanNearQuery
+
+    @desc
+    Matches spans which are near one another.
+
+    @param {Query || Array} clauses A single SpanQuery or array of SpanQueries
+    @param {Integer} slop The number of intervening unmatched positions
+
+    */
+  ejs.SpanNearQuery = function (clauses, slop) {
+
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.SpanNearQuery
+         @property {Object} query
+         */
+    var i, len,
+      query = {
+        span_near: {
+          clauses: [],
+          slop: slop
+        }
+      };
+    
+    if (isQuery(clauses)) {
+      query.span_near.clauses.push(clauses._self());
+    } else if (isArray(clauses)) {
+      for (i = 0, len = clauses.length; i < len; i++) {
+        if (!isQuery(clauses[i])) {
+          throw new TypeError('Argument must be array of SpanQueries');
+        }
+        
+        query.span_near.clauses.push(clauses[i]._self());
+      }
+    } else {
+      throw new TypeError('Argument must be SpanQuery or array of SpanQueries');
+    }
+
+    return {
+
+      /**
+            Sets the clauses used.  If passed a single SpanQuery, it is added
+            to the existing list of clauses.  If passed an array of
+            SpanQueries, they replace any existing clauses.
+
+            @member ejs.SpanNearQuery
+            @param {Query || Array} clauses A SpanQuery or array of SpanQueries.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      clauses: function (clauses) {
+        var i, len;
+        
+        if (clauses == null) {
+          return query.span_near.clauses;
+        }
+      
+        if (isQuery(clauses)) {
+          query.span_near.clauses.push(clauses._self());
+        } else if (isArray(clauses)) {
+          query.span_near.clauses = [];
+          for (i = 0, len = clauses.length; i < len; i++) {
+            if (!isQuery(clauses[i])) {
+              throw new TypeError('Argument must be array of SpanQueries');
+            }
+
+            query.span_near.clauses.push(clauses[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be SpanQuery or array of SpanQueries');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the maximum number of intervening unmatched positions.
+
+            @member ejs.SpanNearQuery
+            @param {Number} distance The number of intervening unmatched positions.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      slop: function (distance) {
+        if (distance == null) {
+          return query.span_near.slop;
+        }
+      
+        query.span_near.slop = distance;
+        return this;
+      },
+
+      /**
+            Sets whether or not matches are required to be in-order.
+
+            @member ejs.SpanNearQuery
+            @param {Boolean} trueFalse Determines if matches must be in-order.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      inOrder: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.span_near.in_order;
+        }
+      
+        query.span_near.in_order = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets whether or not payloads are being used. A payload is an arbitrary
+            byte array stored at a specific position (i.e. token/term).
+
+            @member ejs.SpanNearQuery
+            @param {Boolean} trueFalse Whether or not to return payloads.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      collectPayloads: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.span_near.collect_payloads;
+        }
+      
+        query.span_near.collect_payloads = trueFalse;
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.SpanNearQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.span_near.boost;
+        }
+
+        query.span_near.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.SpanNearQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.SpanNearQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.SpanNearQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Removes matches which overlap with another span query.
+    The span not query maps to Lucene SpanNotQuery.</p>
+
+    @name ejs.SpanNotQuery
+
+    @desc
+    Removes matches which overlap with another span query.
+
+    @param {Query} includeQry a valid SpanQuery whose matching docs will be returned.
+    @param {Query} excludeQry a valid SpanQuery whose matching docs will not be returned
+    
+    */
+  ejs.SpanNotQuery = function (includeQry, excludeQry) {
+
+    if (!isQuery(includeQry) || !isQuery(excludeQry)) {
+      throw new TypeError('Argument must be a SpanQuery');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.SpanNotQuery
+         @property {Object} query
+         */
+    var query = {
+      span_not: {
+        include: includeQry._self(),
+        exclude: excludeQry._self()
+      }
+    };
+
+    return {
+
+      /**
+            Set the span query whose matches are filtered.
+
+            @member ejs.SpanNotQuery
+            @param {Object} spanQuery Any valid span type query.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      include: function (spanQuery) {
+        if (spanQuery == null) {
+          return query.span_not.include;
+        }
+      
+        if (!isQuery(spanQuery)) {
+          throw new TypeError('Argument must be a SpanQuery');
+        }
+        
+        query.span_not.include = spanQuery._self();
+        return this;
+      },
+
+      /**
+            Sets the span query whose matches must not overlap those returned.
+
+            @member ejs.SpanNotQuery
+            @param {Object} spanQuery Any valid span type query.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      exclude: function (spanQuery) {
+        if (spanQuery == null) {
+          return query.span_not.exclude;
+        }
+      
+        if (!isQuery(spanQuery)) {
+          throw new TypeError('Argument must be a SpanQuery');
+        }
+        
+        query.span_not.exclude = spanQuery._self();
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.SpanNotQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.span_not.boost;
+        }
+
+        query.span_not.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.SpanNotQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.SpanNotQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.SpanNotQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The spanOrQuery takes an array of SpanQuerys and will match if any of the
+    underlying SpanQueries match. The span or query maps to Lucene SpanOrQuery.</p>
+
+    @name ejs.SpanOrQuery
+
+    @desc
+    Matches the union of its span clauses.
+
+    @param {Object} clauses A single SpanQuery or array of SpanQueries.
+
+    */
+  ejs.SpanOrQuery = function (clauses) {
+
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.SpanOrQuery
+         @property {Object} query
+         */
+    var i, 
+      len,
+      query = {
+        span_or: {
+          clauses: []
+        }
+      };
+
+    if (isQuery(clauses)) {
+      query.span_or.clauses.push(clauses._self());
+    } else if (isArray(clauses)) {
+      for (i = 0, len = clauses.length; i < len; i++) {
+        if (!isQuery(clauses[i])) {
+          throw new TypeError('Argument must be array of SpanQueries');
+        }
+        
+        query.span_or.clauses.push(clauses[i]._self());
+      }
+    } else {
+      throw new TypeError('Argument must be SpanQuery or array of SpanQueries');
+    }
+
+    return {
+
+      /**
+            Sets the clauses used.  If passed a single SpanQuery, it is added
+            to the existing list of clauses.  If passed an array of
+            SpanQueries, they replace any existing clauses.
+
+            @member ejs.SpanOrQuery
+            @param {Query || Array} clauses A SpanQuery or array of SpanQueries.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      clauses: function (clauses) {
+        var i, len;
+        
+        if (clauses == null) {
+          return query.span_or.clauses;
+        }
+      
+        if (isQuery(clauses)) {
+          query.span_or.clauses.push(clauses._self());
+        } else if (isArray(clauses)) {
+          query.span_or.clauses = [];
+          for (i = 0, len = clauses.length; i < len; i++) {
+            if (!isQuery(clauses[i])) {
+              throw new TypeError('Argument must be array of SpanQueries');
+            }
+
+            query.span_or.clauses.push(clauses[i]._self());
+          }
+        } else {
+          throw new TypeError('Argument must be SpanQuery or array of SpanQueries');
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.SpanOrQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.span_or.boost;
+        }
+
+        query.span_or.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.SpanOrQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.SpanOrQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.SpanOrQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A spanTermQuery is the basic unit of Lucene's Span Query which allows for nested,
+    positional restrictions when matching documents. The spanTermQuery simply matches
+    spans containing a term. It's essentially a termQuery with positional information asscoaited.</p>
+
+    @name ejs.SpanTermQuery
+
+    @desc
+    Matches spans containing a term
+
+    @param {String} field the document field/field to query against
+    @param {String} value the literal value to be matched
+    */
+  ejs.SpanTermQuery = function (field, value) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.SpanTermQuery
+         @property {Object} query
+         */
+    var query = {
+      span_term: {}
+    };
+
+    query.span_term[field] = {
+      term: value
+    };
+
+    return {
+
+      /**
+            Sets the field to query against.
+
+            @member ejs.SpanTermQuery
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = query.span_term[field];
+      
+        if (f == null) {
+          return field;
+        }
+
+        delete query.span_term[field];
+        field = f;
+        query.span_term[f] = oldValue;
+      
+        return this;
+      },
+    
+      /**
+            Sets the term.
+
+            @member ejs.SpanTermQuery
+            @param {String} t A single term.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      term: function (t) {
+        if (t == null) {
+          return query.span_term[field].term;
+        }
+
+        query.span_term[field].term = t;
+        return this;
+      },
+      
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.SpanTermQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.span_term[field].boost;
+        }
+
+        query.span_term[field].boost = boost;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.SpanTermQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.SpanTermQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.SpanTermQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A <code>TermQuery</code> can be used to return documents containing a given
+    keyword or <em>term</em>. For instance, you might want to retieve all the
+    documents/objects that contain the term <code>Javascript</code>. Term filters
+    often serve as the basis for more complex queries such as <em>Boolean</em> queries.</p>
+
+    @name ejs.TermQuery
+
+    @desc
+    A Query that matches documents containing a term. This may be
+    combined with other terms with a BooleanQuery.
+
+    @param {String} field the document field/key to query against
+    @param {String} term the literal value to be matched
+    */
+  ejs.TermQuery = function (field, term) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.TermQuery
+         @property {Object} query
+         */
+    var query = {
+      term: {}
+    };
+
+    query.term[field] = {
+      term: term
+    };
+
+    return {
+
+      /**
+            Sets the fields to query against.
+
+            @member ejs.TermQuery
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = query.term[field];
+      
+        if (f == null) {
+          return field;
+        }
+
+        delete query.term[field];
+        field = f;
+        query.term[f] = oldValue;
+      
+        return this;
+      },
+    
+      /**
+            Sets the term.
+
+            @member ejs.TermQuery
+            @param {String} t A single term.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      term: function (t) {
+        if (t == null) {
+          return query.term[field].term;
+        }
+
+        query.term[field].term = t;
+        return this;
+      },
+      
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.TermQuery
+            @param {Number} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.term[field].boost;
+        }
+
+        query.term[field].boost = boost;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.TermQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TermQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.TermQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A query that match on any (configurable) of the provided terms. This is 
+    a simpler syntax query for using a bool query with several term queries 
+    in the should clauses.</p>
+
+    @name ejs.TermsQuery
+
+    @desc
+    A Query that matches documents containing provided terms. 
+
+    @param {String} field the document field/key to query against
+    @param {String || Array} terms a single term or array of "terms" to match
+    */
+  ejs.TermsQuery = function (field, terms) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.TermsQuery
+         @property {Object} query
+         */
+    var query = {
+      terms: {}
+    };
+    
+    if (isString(terms)) {
+      query.terms[field] = [terms];
+    } else if (isArray(terms)) {
+      query.terms[field] = terms;
+    } else {
+      throw new TypeError('Argument must be string or array');
+    }
+    
+    return {
+
+      /**
+            Sets the fields to query against.
+
+            @member ejs.TermsQuery
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = query.terms[field];
+      
+        if (f == null) {
+          return field;
+        }
+
+        delete query.terms[field];
+        field = f;
+        query.terms[f] = oldValue;
+      
+        return this;
+      },
+    
+      /**
+            Sets the terms.  If you t is a String, it is added to the existing
+            list of terms.  If t is an array, the list of terms replaces the
+            existing terms.
+
+            @member ejs.TermsQuery
+            @param {String || Array} t A single term or an array or terms.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      terms: function (t) {
+        if (t == null) {
+          return query.terms[field];
+        }
+
+        if (isString(t)) {
+          query.terms[field].push(t);
+        } else if (isArray(t)) {
+          query.terms[field] = t;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+      
+        return this;
+      },
+
+      /**
+            Sets the minimum number of terms that need to match in a document
+            before that document is returned in the results.
+
+            @member ejs.TermsQuery
+            @param {Integer} min A positive integer.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minimumShouldMatch: function (min) {
+        if (min == null) {
+          return query.terms.minimum_should_match;
+        }
+      
+        query.terms.minimum_should_match = min;
+        return this;
+      },
+      
+      /**
+            Enables or disables similarity coordinate scoring of documents
+            matching the <code>Query</code>. Default: false.
+
+            @member ejs.TermsQuery
+            @param {String} trueFalse A <code>true/false</code value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      disableCoord: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.terms.disable_coord;
+        }
+
+        query.terms.disable_coord = trueFalse;
+        return this;
+      },
+            
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.TermsQuery
+            @param {Number} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.terms.boost;
+        }
+
+        query.terms.boost = boost;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.TermsQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TermsQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.TermsQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>TThe top_children query runs the child query with an estimated hits size, 
+    and out of the hit docs, aggregates it into parent docs. If there aren’t 
+    enough parent docs matching the requested from/size search request, then it 
+    is run again with a wider (more hits) search.</p>
+
+    <p>The top_children also provide scoring capabilities, with the ability to 
+    specify max, sum or avg as the score type.</p>
+
+    @name ejs.TopChildrenQuery
+
+    @desc
+    Returns child documents matching the query aggregated into the parent docs.
+
+    @param {Object} qry A valid query object.
+    @param {String} type The child type to execute the query on
+    */
+  ejs.TopChildrenQuery = function (qry, type) {
+
+    if (!isQuery(qry)) {
+      throw new TypeError('Argument must be a Query');
+    }
+    
+    /**
+         The internal query object. <code>Use _self()</code>
+         @member ejs.TopChildrenQuery
+         @property {Object} query
+         */
+    var query = {
+      top_children: {
+        query: qry._self(),
+        type: type
+      }
+    };
+
+    return {
+
+      /**
+            Sets the query
+
+            @member ejs.TopChildrenQuery
+            @param {Object} q A valid Query object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (q) {
+        if (q == null) {
+          return query.top_children.query;
+        }
+  
+        if (!isQuery(q)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.top_children.query = q._self();
+        return this;
+      },
+
+      /**
+            Sets the child document type to search against
+
+            @member ejs.TopChildrenQuery
+            @param {String} t A valid type name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (t) {
+        if (t == null) {
+          return query.top_children.type;
+        }
+  
+        query.top_children.type = t;
+        return this;
+      },
+
+      /**
+            Sets the scope of the query.  A scope allows to run facets on the 
+            same scope name that will work against the child documents. 
+
+            @member ejs.TopChildrenQuery
+            @param {String} s The scope name as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scope: function (s) {
+        if (s == null) {
+          return query.top_children._scope;
+        }
+  
+        query.top_children._scope = s;
+        return this;
+      },
+
+      /**
+            Sets the scoring type.  Valid values are max, sum, or avg. If
+            another value is passed it we silently ignore the value.
+
+            @member ejs.TopChildrenQuery
+            @param {String} s The scoring type as a string. 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      score: function (s) {
+        if (s == null) {
+          return query.top_children.score;
+        }
+  
+        s = s.toLowerCase();
+        if (s === 'max' || s === 'sum' || s === 'avg') {
+          query.top_children.score = s;
+        }
+      
+        return this;
+      },
+  
+      /**
+            Sets the factor which is the number of hits that are asked for in
+            the child query.  Defaults to 5.
+
+            @member ejs.TopChildrenQuery
+            @param {Integer} f A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      factor: function (f) {
+        if (f == null) {
+          return query.top_children.factor;
+        }
+
+        query.top_children.factor = f;
+        return this;
+      },
+
+      /**
+            Sets the incremental factor.  The incremental factor is used when not
+            enough child documents are returned so the factor is multiplied by
+            the incremental factor to fetch more results.  Defaults to 52
+
+            @member ejs.TopChildrenQuery
+            @param {Integer} f A positive integer value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      incrementalFactor: function (f) {
+        if (f == null) {
+          return query.top_children.incremental_factor;
+        }
+
+        query.top_children.incremental_factor = f;
+        return this;
+      },
+        
+      /**
+            Sets the boost value of the <code>Query</code>.
+
+            @member ejs.TopChildrenQuery
+            @param {Double} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.top_children.boost;
+        }
+
+        query.top_children.boost = boost;
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.TopChildrenQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.TopChildrenQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.TopChildrenQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Matches documents that have fields matching a wildcard expression 
+    (not analyzed). Supported wildcards are *, which matches any character 
+    sequence (including the empty one), and ?, which matches any single 
+    character. Note this query can be slow, as it needs to iterate over many 
+    wildcards. In order to prevent extremely slow wildcard queries, a wildcard 
+    wildcard should not start with one of the wildcards * or ?. The wildcard query 
+    maps to Lucene WildcardQuery.</p>
+
+    @name ejs.WildcardQuery
+
+    @desc
+    A Query that matches documents containing a wildcard. This may be
+    combined with other wildcards with a BooleanQuery.
+
+    @param {String} field the document field/key to query against
+    @param {String} value the literal value to be matched
+    */
+  ejs.WildcardQuery = function (field, value) {
+
+    /**
+         The internal query object. <code>Use get()</code>
+         @member ejs.WildcardQuery
+         @property {Object} query
+         */
+    var query = {
+      wildcard: {}
+    };
+
+    query.wildcard[field] = {
+      value: value
+    };
+
+    return {
+
+      /**
+            Sets the fields to query against.
+
+            @member ejs.WildcardQuery
+            @param {String} f A valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = query.wildcard[field];
+    
+        if (f == null) {
+          return field;
+        }
+
+        delete query.wildcard[field];
+        field = f;
+        query.wildcard[f] = oldValue;
+    
+        return this;
+      },
+  
+      /**
+            Sets the wildcard query value.
+
+            @member ejs.WildcardQuery
+            @param {String} v A single term.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      value: function (v) {
+        if (v == null) {
+          return query.wildcard[field].value;
+        }
+
+        query.wildcard[field].value = v;
+        return this;
+      },
+    
+      /**
+            Sets rewrite method.  Valid values are: 
+            
+            constant_score_auto - tries to pick the best constant-score rewrite 
+              method based on term and document counts from the query
+              
+            scoring_boolean - translates each term into boolean should and 
+              keeps the scores as computed by the query
+              
+            constant_score_boolean - same as scoring_boolean, expect no scores
+              are computed.
+              
+            constant_score_filter - first creates a private Filter, by visiting 
+              each term in sequence and marking all docs for that term
+              
+            top_terms_boost_N - first translates each term into boolean should
+              and scores are only computed as the boost using the top N
+              scoring terms.  Replace N with an integer value.
+              
+            top_terms_N -   first translates each term into boolean should
+                and keeps the scores as computed by the query. Only the top N
+                scoring terms are used.  Replace N with an integer value.
+            
+            Default is constant_score_auto.
+
+            This is an advanced option, use with care.
+
+            @member ejs.WildcardQuery
+            @param {String} m The rewrite method as a string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      rewrite: function (m) {
+        if (m == null) {
+          return query.wildcard[field].rewrite;
+        }
+        
+        m = m.toLowerCase();
+        if (m === 'constant_score_auto' || m === 'scoring_boolean' ||
+          m === 'constant_score_boolean' || m === 'constant_score_filter' ||
+          m.indexOf('top_terms_boost_') === 0 || 
+          m.indexOf('top_terms_') === 0) {
+            
+          query.wildcard[field].rewrite = m;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the boost value for documents matching the <code>Query</code>.
+
+            @member ejs.WildcardQuery
+            @param {Number} boost A positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boost: function (boost) {
+        if (boost == null) {
+          return query.wildcard[field].boost;
+        }
+
+        query.wildcard[field].boost = boost;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.WildcardQuery
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.WildcardQuery
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'query';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.WildcardQuery
+            @returns {String} returns this object's internal <code>query</code> property.
+            */
+      _self: function () {
+        return query;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A GeoPoint object that can be used in queries and filters that 
+    take a GeoPoint.  GeoPoint supports various input formats.</p>
+
+    <p>See http://www.elasticsearch.org/guide/reference/mapping/geo-point-type.html</p>
+
+    @name ejs.GeoPoint
+
+    @desc
+    <p>Defines a point</p>
+
+    @param {Array} p An optional point as an array in [lat, lon] format.
+    */
+  ejs.GeoPoint = function (p) {
+
+    var point = [0, 0];
+
+    // p  = [lat, lon], convert it to GeoJSON format of [lon, lat]
+    if (p != null && isArray(p) && p.length === 2) {
+      point = [p[1], p[0]];
+    }
+  
+    return {
+
+      /**
+            Sets the GeoPoint as properties on an object.  The object must have
+            a 'lat' and 'lon' property.  
+          
+            Example:
+            {lat: 41.12, lon: -71.34}
+
+            @member ejs.GeoPoint
+            @param {Object} obj an object with a lat and lon property.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      properties: function (obj) {
+        if (obj == null) {
+          return point;
+        }
+      
+        if (isObject(obj) && has(obj, 'lat') && has(obj, 'lon')) {
+          point = {
+            lat: obj.lat,
+            lon: obj.lon
+          };
+        }
+      
+        return this;
+      },
+
+      /**
+            Sets the GeoPoint as a string.  The format is "lat,lon".
+          
+            Example:
+          
+            "41.12,-71.34"
+
+            @member ejs.GeoPoint
+            @param {String} s a String point in "lat,lon" format.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      string: function (s) {
+        if (s == null) {
+          return point;
+        }
+      
+        if (isString(s) && s.indexOf(',') !== -1) {
+          point = s;
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets the GeoPoint as a GeoHash.  The hash is a string of 
+            alpha-numeric characters with a precision length that defaults to 12.
+          
+            Example:
+            "drm3btev3e86"
+
+            @member ejs.GeoPoint
+            @param {String} hash an GeoHash as a string
+            @param {Integer} precision an optional precision length, defaults
+              to 12 if not specified.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      geohash: function (hash, precision) {
+        // set precision, default to 12
+        precision = (precision != null && isNumber(precision)) ? precision : 12;
+      
+        if (hash == null) {
+          return point;
+        }
+      
+        if (isString(hash) && hash.length === precision) {
+          point = hash;
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets the GeoPoint from an array point.  The array must contain only
+            2 values.  The first value is the lat and the 2nd value is the lon.
+          
+            Example:
+            [41.12, -71.34]
+
+            @member ejs.GeoPoint
+            @param {Array} a an array of length 2.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      array: function (a) {
+        if (a == null) {
+          return point;
+        }
+      
+      
+        // convert to GeoJSON format of [lon, lat]
+        if (isArray(a) && a.length === 2) {
+          point = [a[1], a[0]];
+        }
+      
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.GeoPoint
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(point);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.GeoPoint
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'geo point';
+      },
+      
+      /**
+            Retrieves the internal <code>script</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.GeoPoint
+            @returns {String} returns this object's internal object representation.
+            */
+      _self: function () {
+        return point;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>Allows to highlight search results on one or more fields.  In order to 
+    perform highlighting, the actual content of the field is required. If the 
+    field in question is stored (has store set to yes in the mapping), it will 
+    be used, otherwise, the actual _source will be loaded and the relevant 
+    field will be extracted from it.</p>
+
+    <p>If no term_vector information is provided (by setting it to 
+    with_positions_offsets in the mapping), then the plain highlighter will be 
+    used. If it is provided, then the fast vector highlighter will be used. 
+    When term vectors are available, highlighting will be performed faster at 
+    the cost of bigger index size.</p>
+
+    <p>See http://www.elasticsearch.org/guide/reference/api/search/highlighting.html</p>
+
+    @name ejs.Highlight
+
+    @desc
+    <p>Allows to highlight search results on one or more fields.</p>
+
+    @param {String || Array} fields An optional field or array of fields to highlight.
+    */
+  ejs.Highlight = function (fields) {
+  
+    var highlight = {
+      fields: {}
+    },
+  
+    addOption = function (field, option, val) {
+      if (field == null) {
+        highlight[option] = val;
+      } else {
+        if (!has(highlight.fields, field)) {
+          highlight.fields[field] = {};
+        }
+      
+        highlight.fields[field][option] = val;
+      }
+    };
+
+    if (fields != null) {
+      if (isString(fields)) {
+        highlight.fields[fields] = {};
+      } else if (isArray(fields)) {
+        each(fields, function (field) {
+          highlight.fields[field] = {};
+        });
+      }
+    }
+  
+    return {
+
+      /**
+            Allows you to set the fields that will be highlighted.  You can 
+            specify a single field or an array of fields.  All fields are 
+            added to the current list of fields.
+
+            @member ejs.Highlight
+            @param {String || Array} vals A field name or array of field names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fields: function (vals) {
+        if (vals == null) {
+          return highlight.fields;
+        }
+      
+        if (isString(vals)) {
+          if (!has(highlight.fields, vals)) {
+            highlight.fields[vals] = {};
+          }
+        } else if (isArray(vals)) {
+          each(vals, function (field) {
+            if (!has(highlight.fields, field)) {
+              highlight.fields[field] = {};
+            }
+          });
+        }
+      },
+    
+      /**
+            Sets the pre tags for highlighted fragments.  You can apply the
+            tags to a specific field by passing the field name in to the 
+            <code>oField</code> parameter.
+        
+            @member ejs.Highlight
+            @param {String || Array} tags A single tag or an array of tags.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      preTags: function (tags, oField) {
+        if (tags === null && oField != null) {
+          return highlight.fields[oField].pre_tags;
+        } else if (tags == null) {
+          return highlight.pre_tags;
+        }
+  
+        if (isString(tags)) {
+          addOption(oField, 'pre_tags', [tags]);
+        } else if (isArray(tags)) {
+          addOption(oField, 'pre_tags', tags);
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the post tags for highlighted fragments.  You can apply the
+            tags to a specific field by passing the field name in to the 
+            <code>oField</code> parameter.
+        
+            @member ejs.Highlight
+            @param {String || Array} tags A single tag or an array of tags.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      postTags: function (tags, oField) {
+        if (tags === null && oField != null) {
+          return highlight.fields[oField].post_tags;
+        } else if (tags == null) {
+          return highlight.post_tags;
+        }
+  
+        if (isString(tags)) {
+          addOption(oField, 'post_tags', [tags]);
+        } else if (isArray(tags)) {
+          addOption(oField, 'post_tags', tags);
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the order of highlight fragments.  You can apply the option
+            to a specific field by passing the field name in to the 
+            <code>oField</code> parameter.  Valid values for order are:
+            
+            score - the score calculated by Lucene's highlighting framework.
+        
+            @member ejs.Highlight
+            @param {String} o The order.  Currently only "score".
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      order: function (o, oField) {
+        if (o === null && oField != null) {
+          return highlight.fields[oField].order;
+        } else if (o == null) {
+          return highlight.order;
+        }
+  
+        o = o.toLowerCase();
+        if (o === 'score') {
+          addOption(oField, 'order', o);
+        }
+        
+        return this;
+      },
+      
+      /**
+            Sets the schema to be used for the tags. Valid values are:
+            
+            styled - 10 <em> pre tags with css class of hltN, where N is 1-10
+        
+            @member ejs.Highlight
+            @param {String} s The schema.  Currently only "styled".
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      tagsSchema: function (s) {
+        if (s == null) {
+          return highlight.tags_schema;
+        }
+  
+        s = s.toLowerCase();
+        if (s === 'styled') {
+          highlight.tags_schema = s;
+        }
+        
+        return this;
+      },
+      
+      /**
+            Enables highlights in documents matched by a filter.  
+            You can apply the option to a specific field by passing the field 
+            name in to the <code>oField</code> parameter.  Defaults to false.
+            
+            @member ejs.Highlight
+            @param {Boolean} trueFalse If filtered docs should be highlighted.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      highlightFilter: function (trueFalse, oField) {
+        if (trueFalse === null && oField != null) {
+          return highlight.fields[oField].highlight_filter;
+        } else if (trueFalse == null) {
+          return highlight.highlight_filter;
+        }
+  
+        addOption(oField, 'highlight_filter', trueFalse);
+        return this;
+      },
+      
+      /**
+            Sets the size of each highlight fragment in characters.  
+            You can apply the option to a specific field by passing the field 
+            name in to the <code>oField</code> parameter. Default:  100
+            
+            @member ejs.Highlight
+            @param {Integer} size The fragment size in characters.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fragmentSize: function (size, oField) {
+        if (size === null && oField != null) {
+          return highlight.fields[oField].fragment_size;
+        } else if (size == null) {
+          return highlight.fragment_size;
+        }
+  
+        addOption(oField, 'fragment_size', size);
+        return this;
+      },
+      
+      /**
+            Sets the number of highlight fragments.
+            You can apply the option to a specific field by passing the field 
+            name in to the <code>oField</code> parameter. Default:  5
+
+            @member ejs.Highlight
+            @param {Integer} cnt The fragment size in characters.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      numberOfFragments: function (cnt, oField) {
+        if (cnt === null && oField != null) {
+          return highlight.fields[oField].number_of_fragments;
+        } else if (cnt == null) {
+          return highlight.number_of_fragments;
+        }
+
+        addOption(oField, 'number_of_fragments', cnt);
+        return this;
+      },       
+
+      /**
+            Sets highlight encoder.  Valid values are:
+            
+            default - the default, no encoding
+            html - to encode html characters if you use html tags
+        
+            @member ejs.Highlight
+            @param {String} e The encoder.  default or html
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      encoder: function (e) {
+        if (e == null) {
+          return highlight.encoder;
+        }
+  
+        e = e.toLowerCase();
+        if (e === 'default' || e === 'html') {
+          highlight.encoder = e;
+        }
+        
+        return this;
+      },
+
+      /**
+            When enabled it will cause a field to be highlighted only if a 
+            query matched that field. false means that terms are highlighted 
+            on all requested fields regardless if the query matches 
+            specifically on them.  You can apply the option to a specific 
+            field by passing the field name in to the <code>oField</code> 
+            parameter.  Defaults to false.
+            
+            @member ejs.Highlight
+            @param {Boolean} trueFalse If filtered docs should be highlighted.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      requireFieldMatch: function (trueFalse, oField) {
+        if (trueFalse === null && oField != null) {
+          return highlight.fields[oField].require_field_match;
+        } else if (trueFalse == null) {
+          return highlight.require_field_match;
+        }
+  
+        addOption(oField, 'require_field_match', trueFalse);
+        return this;
+      },
+
+      /**
+            Sets the max number of characters to scan while looking for the 
+            start of a boundary character. You can apply the option to a 
+            specific field by passing the field name in to the 
+            <code>oField</code> parameter. Default:  20
+
+            @member ejs.Highlight
+            @param {Integer} cnt The max characters to scan.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boundaryMaxScan: function (cnt, oField) {
+        if (cnt === null && oField != null) {
+          return highlight.fields[oField].boundary_max_scan;
+        } else if (cnt == null) {
+          return highlight.boundary_max_scan;
+        }
+
+        addOption(oField, 'boundary_max_scan', cnt);
+        return this;
+      },       
+
+      /**
+            Set's the boundary characters.  When highlighting a field that is 
+            mapped with term vectors, boundary_chars can be configured to 
+            define what constitutes a boundary for highlighting. It’s a single 
+            string with each boundary character defined in it. You can apply
+            the option to a specific field by passing the field name in to 
+            the <code>oField</code> parameter. It defaults to ".,!? \t\n".
+            
+            @member ejs.Highlight
+            @param {String} charStr The boundary chars in a string.
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      boundaryChars: function (charStr, oField) {
+        if (charStr === null && oField != null) {
+          return highlight.fields[oField].boundary_chars;
+        } else if (charStr == null) {
+          return highlight.boundary_chars;
+        }
+  
+        addOption(oField, 'boundary_chars', charStr);
+        return this;
+      },
+      
+      /**
+            Sets the highligher type.  You can apply the option
+            to a specific field by passing the field name in to the 
+            <code>oField</code> parameter.  Valid values for order are:
+            
+            fast-vector-highlighter - the fast vector based highligher
+            highlighter - the slower plain highligher
+        
+            @member ejs.Highlight
+            @param {String} t The highligher. 
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (t, oField) {
+        if (t === null && oField != null) {
+          return highlight.fields[oField].type;
+        } else if (t == null) {
+          return highlight.type;
+        }
+  
+        t = t.toLowerCase();
+        if (t === 'fast-vector-highlighter' || t === 'highlighter') {
+          addOption(oField, 'type', t);
+        }
+        
+        return this;
+      },
+
+      /**
+            Sets the fragmenter type.  You can apply the option
+            to a specific field by passing the field name in to the 
+            <code>oField</code> parameter.  Valid values for order are:
+            
+            simple - breaks text up into same-size fragments with no concerns 
+              over spotting sentence boundaries.
+            span - breaks text up into same-size fragments but does not split 
+              up Spans.
+            
+            @member ejs.Highlight
+            @param {String} f The fragmenter. 
+            @param {String} oField An optional field name
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fragmenter: function (f, oField) {
+        if (f === null && oField != null) {
+          return highlight.fields[oField].fragmenter;
+        } else if (f == null) {
+          return highlight.fragmenter;
+        }
+  
+        f = f.toLowerCase();
+        if (f === 'simple' || f === 'span') {
+          addOption(oField, 'fragmenter', f);
+        }
+        
+        return this;
+      },
+      
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.Highlight
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(highlight);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+          
+            @member ejs.Highlight
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'highlight';
+      },
+    
+      /**
+            Retrieves the internal <code>script</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.Highlight
+            @returns {String} returns this object's internal object representation.
+            */
+      _self: function () {
+        return highlight;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A shape which has already been indexed in another index and/or index 
+    type. This is particularly useful for when you have a pre-defined list of 
+    shapes which are useful to your application and you want to reference this 
+    using a logical name (for example ‘New Zealand’) rather than having to 
+    provide their coordinates each time.</p>
+
+    @name ejs.IndexedShape
+
+    @desc
+    <p>Defines a shape that already exists in an index/type.</p>
+
+    @param {String} type The name of the type where the shape is indexed.
+    @param {String} id The document id of the shape.
+
+    */
+  ejs.IndexedShape = function (type, id) {
+
+    var indexedShape = {
+      type: type,
+      id: id
+    };
+
+    return {
+
+      /**
+            Sets the type which the shape is indexed under.
+
+            @member ejs.IndexedShape
+            @param {String} t a valid shape type.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (t) {
+        if (t == null) {
+          return indexedShape.type;
+        }
+    
+        indexedShape.type = t;
+        return this;
+      },
+
+      /**
+            Sets the document id of the indexed shape.
+
+            @member ejs.IndexedShape
+            @param {String} id a valid document id.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      id: function (id) {
+        if (id == null) {
+          return indexedShape.id;
+        }
+    
+        indexedShape.id = id;
+        return this;
+      },
+
+      /**
+            Sets the index which the shape is indexed under. 
+            Defaults to "shapes".
+
+            @member ejs.IndexedShape
+            @param {String} idx a valid index name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      index: function (idx) {
+        if (idx == null) {
+          return indexedShape.index;
+        }
+    
+        indexedShape.index = idx;
+        return this;
+      },
+
+      /**
+            Sets the field name containing the indexed shape. 
+            Defaults to "shape".
+
+            @member ejs.IndexedShape
+            @param {String} field a valid field name.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      shapeFieldName: function (field) {
+        if (field == null) {
+          return indexedShape.shape_field_name;
+        }
+    
+        indexedShape.shape_field_name = field;
+        return this;
+      },
+              
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.IndexedShape
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(indexedShape);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.IndexedShape
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'indexed shape';
+      },
+      
+      /**
+            Retrieves the internal <code>script</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.IndexedShape
+            @returns {String} returns this object's internal object representation.
+            */
+      _self: function () {
+        return indexedShape;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>The <code>Request</code> object provides methods generating and 
+    executing search requests.</p>
+
+    @name ejs.Request
+
+    @desc
+    <p>Provides methods for executing search requests</p>
+
+    @param {Object} conf A configuration object containing the initilization
+      parameters.  The following parameters can be set in the conf object:
+        indices - single index name or array of index names
+        types - single type name or array of types
+        routing - the shard routing value
+    */
+  ejs.Request = function (conf) {
+
+    var query, indices, types, routing;
+
+    /**
+        The internal query object.
+        @member ejs.Request
+        @property {Object} query
+        */
+    query = {};
+
+    conf = conf || {};
+    // check if we are searching across any specific indeices        
+    if (conf.indices == null) {
+      indices = [];
+    } else if (isString(conf.indices)) {
+      indices = [conf.indices];
+    } else {
+      indices = conf.indices;
+    }
+
+    // check if we are searching across any specific types
+    if (conf.types == null) {
+      types = [];
+    } else if (isString(conf.types)) {
+      types = [conf.types];
+    } else {
+      types = conf.types;
+    }
+
+    // check that an index is specified when a type is
+    // if not, search across _all indices
+    if (indices.length === 0 && types.length > 0) {
+      indices = ["_all"];
+    }
+
+    if (conf.routing != null) {
+      routing = conf.routing;
+    } else {
+      routing = '';
+    }
+    
+    return {
+
+      /**
+            Sets the sorting for the query.  This accepts many input formats.
+            
+            sort() - The current sorting values are returned.
+            sort(fieldName) - Adds the field to the current list of sorting values.
+            sort(fieldName, order) - Adds the field to the current list of
+              sorting with the specified order.  Order must be asc or desc.
+            sort(ejs.Sort) - Adds the Sort value to the current list of sorting values. 
+            sort(array) - Replaces all current sorting values with values
+              from the array.  The array must contain only strings and Sort
+              objects.
+
+            Multi-level sorting is supported so the order in which sort fields 
+            are added to the query requests is relevant.
+            
+            It is recommended to use <code>Sort</code> objects when possible.
+            
+            @member ejs.Request
+            @param {String} fieldName The field to be sorted by.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      sort: function () {
+        var i, len;
+        
+        if (!has(query, "sort")) {
+          query.sort = [];
+        }
+
+        if (arguments.length === 0) {
+          return query.sort;
+        }
+      
+        // if passed a single argument
+        if (arguments.length === 1) {
+          var sortVal = arguments[0];
+          
+          if (isString(sortVal)) {
+            // add  a single field name
+            query.sort.push(sortVal);
+          } else if (isSort(sortVal)) {
+            // add the Sort object
+            query.sort.push(sortVal._self());
+          } else if (isArray(sortVal)) {
+            // replace with all values in the array
+            // the values must be a fieldName (string) or a
+            // Sort object.  Any other type throws an Error.
+            query.sort = [];
+            for (i = 0, len = sortVal.length; i < len; i++) {
+              if (isString(sortVal[i])) {
+                query.sort.push(sortVal[i]);
+              } else if (isSort(sortVal[i])) {
+                query.sort.push(sortVal[i]._self());
+              } else {
+                throw new TypeError('Invalid object in array');
+              }
+            }
+          } else {
+            // Invalid object type as argument.
+            throw new TypeError('Argument must be string, Sort, or array');
+          } 
+        } else if (arguments.length === 2) {
+          // handle the case where a single field name and order are passed
+          var field = arguments[0],
+            order = arguments[1];
+            
+          if (isString(field) && isString(order)) {
+            order = order.toLowerCase();
+            if (order === 'asc' || order === 'desc') {
+              var sortObj = {};
+              sortObj[field] = {order: order};
+              query.sort.push(sortObj);
+            }
+          }
+        }
+
+        return this;
+      },
+
+      /**
+           Enables score computation and tracking during sorting.  Be default, 
+           when sorting scores are not computed.
+
+            @member ejs.Request
+            @param {Boolean} trueFalse If scores should be computed and tracked.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      trackScores: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.track_scores;
+        }
+      
+        query.track_scores = trueFalse;
+        return this;
+      },
+      
+      /**
+            Sets the number of results/documents to be returned. This is set on a per page basis.
+
+            @member ejs.Request
+            @param {Integer} s The number of results that are to be returned by the search.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      size: function (s) {
+        if (s == null) {
+          return query.size;
+        }
+      
+        query.size = s;
+        return this;
+      },
+
+      /**
+            A search timeout, bounding the search request to be executed 
+            within the specified time value and bail with the hits accumulated 
+            up to that point when expired. Defaults to no timeout.
+
+            @member ejs.Request
+            @param {Long} t The timeout value in milliseconds.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      timeout: function (t) {
+        if (t == null) {
+          return query.timeout;
+        }
+      
+        query.timeout = t;
+        return this;
+      },
+                  
+      /**
+            Sets the shard routing parameter.  Only shards matching routing
+            values will be searched.  Set to an empty string to disable routing.
+            Disabled by default.
+
+            @member ejs.Request
+            @param {String} route The routing values as a comma-separated string.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      routing: function (route) {
+        if (route == null) {
+          return routing;
+        }
+      
+        routing = route;
+        return this;
+      },
+
+      /**
+            By default, searches return full documents, meaning every property or field.
+            This method allows you to specify which fields you want returned.
+            
+            Pass a single field name and it is appended to the current list of
+            fields.  Pass an array of fields and it replaces all existing 
+            fields.
+
+            @member ejs.Request
+            @param {String || Array} s The field as a string or fields as array
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      fields: function (fieldList) {
+        if (fieldList == null) {
+          return query.fields;
+        }
+      
+        if (query.fields == null) {
+          query.fields = [];
+        }
+        
+        if (isString(fieldList)) {
+          query.fields.push(fieldList);
+        } else if (isArray(fieldList)) {
+          query.fields = fieldList;
+        } else {
+          throw new TypeError('Argument must be string or array');
+        }
+        
+        return this;
+      },
+
+      /**
+            A search result set could be very large (think Google). Setting the
+            <code>from</code> parameter allows you to page through the result set
+            by making multiple request. This parameters specifies the starting
+            result/document number point. Combine with <code>size()</code> to achieve paging.
+
+            @member ejs.Request
+            @param {Array} f The offset at which to start fetching results/documents from the result set.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      from: function (f) {
+        if (f == null) {
+          return query.from;
+        }
+        
+        query.from = f;
+        return this;
+      },
+
+      /**
+            Allows you to set the specified query on this search object. This is the
+            query that will be used when the search is executed.
+
+            @member ejs.Request
+            @param {Query} someQuery Any valid <code>Query</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      query: function (someQuery) {
+        if (someQuery == null) {
+          return query.query;
+        }
+      
+        if (!isQuery(someQuery)) {
+          throw new TypeError('Argument must be a Query');
+        }
+        
+        query.query = someQuery._self();
+        return this;
+      },
+
+      /**
+            Allows you to set the specified indices on this request object. This is the
+            set of indices that will be used when the search is executed.
+
+            @member ejs.Request
+            @param {Array} indexArray An array of collection names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      indices: function (indexArray) {
+        if (indexArray == null) {
+          return indices;
+        } else if (isString(indexArray)) {
+          indices = [indexArray];
+        } else if (isArray(indexArray)) {
+          indices = indexArray;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+
+        // check that an index is specified when a type is
+        // if not, search across _all indices
+        if (indices.length === 0 && types.length > 0) {
+          indices = ["_all"];
+        }
+
+        return this;
+      },
+
+      /**
+            Allows you to set the specified content-types on this request object. This is the
+            set of indices that will be used when the search is executed.
+
+            @member ejs.Request
+            @param {Array} typeArray An array of content-type names.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      types: function (typeArray) {
+        if (typeArray == null) {
+          return types;
+        } else if (isString(typeArray)) {
+          types = [typeArray];
+        } else if (isArray(typeArray)) {
+          types = typeArray;
+        } else {
+          throw new TypeError('Argument must be a string or array');
+        }
+
+        // check that an index is specified when a type is
+        // if not, search across _all indices
+        if (indices.length === 0 && types.length > 0) {
+          indices = ["_all"];
+        }
+
+        return this;
+      },
+
+      /**
+            Allows you to set the specified facet on this request object. Multiple facets can
+            be set, all of which will be returned when the search is executed.
+
+            @member ejs.Request
+            @param {Facet} facet Any valid <code>Facet</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      facet: function (facet) {
+        if (facet == null) {
+          return query.facets;
+        }
+      
+        if (query.facets == null) {
+          query.facets = {};
+        }
+      
+        if (!isFacet(facet)) {
+          throw new TypeError('Argument must be a Facet');
+        }
+        
+        extend(query.facets, facet._self());
+
+        return this;
+      },
+
+      /**
+            Allows you to set a specified filter on this request object.
+
+            @member ejs.Request
+            @param {Object} filter Any valid <code>Filter</code> object.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      filter: function (filter) {
+        if (filter == null) {
+          return query.filter;
+        }
+      
+        if (!isFilter(filter)) {
+          throw new TypeError('Argument must be a Filter');
+        }
+        
+        query.filter = filter._self();
+        return this;
+      },
+
+      /**
+            Performs highlighting based on the <code>Highlight</code> 
+            settings.
+
+            @member ejs.Request
+            @param {Highlight} h A valid Highlight object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      highlight: function (h) {
+        if (h == null) {
+          return query.highlight;
+        }
+      
+        if (!isHighlight(h)) {
+          throw new TypeError('Argument must be a Highlight object');
+        }
+
+        query.highlight = h._self();
+        return this;
+      },
+
+      /**
+            Computes a document property dynamically based on the supplied <code>ScriptField</code>.
+
+            @member ejs.Request
+            @param {ScriptField} oScriptField A valid <code>ScriptField</code>.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      scriptField: function (oScriptField) {
+        if (oScriptField == null) {
+          return query.script_fields;
+        }
+      
+        if (query.script_fields == null) {
+          query.script_fields = {};
+        }
+      
+        if (!isScriptField(oScriptField)) {
+          throw new TypeError('Argument must be a ScriptField');
+        }
+        
+        extend(query.script_fields, oScriptField._self());
+        return this;
+      },
+
+      /**
+            Controls a preference of which shard replicas to execute the search request on.
+            By default, the operation is randomized between the each shard replicas.  The
+            preference can be one of the following:
+
+            _primary - the operation will only be executed on primary shards
+            _local - the operation will prefer to be executed on local shards
+            _only_node:$nodeid - the search will only be executed on node with id $nodeid
+            custom - any string, will guarentee searches always happen on same node.
+
+            @member ejs.Request
+            @param {String} perf the preference, any of _primary, _local, _only_:$nodeid, or a custom string value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      preference: function (perf) {
+        if (perf == null) {
+          return query.preference;
+        }
+      
+        query.preference = perf;
+        return this;
+      },
+
+      /**
+            Boosts hits in the specified index by the given boost value.
+
+            @member ejs.Request
+            @param {String} index the index to boost
+            @param {Double} boost the boost value
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      indexBoost: function (index, boost) {
+        if (query.indices_boost == null) {
+          query.indices_boost = {};
+        }
+
+        if (arguments.length === 0) {
+          return query.indices_boost;
+        }
+      
+        query.indices_boost[index] = boost;
+        return this;
+      },
+
+      /**
+            Enable/Disable explanation of score for each search result.
+
+            @member ejs.Request
+            @param {Boolean} trueFalse true to enable, false to disable
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      explain: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.explain;
+        } 
+        
+        query.explain = trueFalse;
+        return this;
+      },
+
+      /**
+            Enable/Disable returning version number for each search result.
+
+            @member ejs.Request
+            @param {Boolean} trueFalse true to enable, false to disable
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      version: function (trueFalse) {
+        if (trueFalse == null) {
+          return query.version;
+        }
+        
+        query.version = trueFalse;
+        return this;
+      },
+
+      /**
+            Filters out search results will scores less than the specified minimum score.
+
+            @member ejs.Request
+            @param {Double} min a positive <code>double</code> value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      minScore: function (min) {
+        if (min == null) {
+          return query.min_score;
+        }
+        
+        query.min_score = min;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.Request
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(query);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.Request
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'request';
+      },
+      
+      /**
+            Retrieves the internal <code>query</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.Request
+            @returns {String} returns this object's internal object representation.
+            */
+      _self: function () {
+        return query;
+      },
+      
+      /**
+            Executes the search. This call runs synchronously when used on the server side.
+            The callback is still executed and the function returns the return value of the callback.
+
+            @member ejs.Request
+            @param {Function} fnCallBack A callback function that handles the search response.
+            @returns {void} Returns the value of the callback when executing on the server.
+            */
+      doSearch: function (fnCallBack) {
+        var 
+          queryData = JSON.stringify(query),
+          searchUrl = '';
+      
+        // make sure the user has set a client
+        if (ejs.client == null) {
+          throw new Error("No Client Set");
+        }
+          
+        // generate the search url
+        if (indices.length > 0) {
+          searchUrl = searchUrl + '/' + indices.join();
+        }
+
+        if (types.length > 0) {
+          searchUrl = searchUrl + '/' + types.join();
+        }
+        
+        searchUrl = searchUrl + '/_search';
+        
+        if (routing !== '') {
+          searchUrl = searchUrl + '?routing=' + encodeURIComponent(routing);
+        }
+
+        return ejs.client.post(searchUrl, queryData, fnCallBack);
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>ScriptField's allow you create dynamic fields on stored documents at query
+    time. For example, you might have a set of document thats containsthe fields
+    <code>price</code> and <code>quantity</code>. At query time, you could define a computed
+    property that dynamically creates a new field called <code>total</code>in each document
+    based on the calculation <code>price * quantity</code>.</p>
+
+    @name ejs.ScriptField
+
+    @desc
+    <p>Computes dynamic document properties based on information from other fields.</p>
+
+    @param {String} fieldName A name of the script field to create.
+
+    */
+  ejs.ScriptField = function (fieldName) {
+    var script = {};
+
+    script[fieldName] = {};
+
+    return {
+
+      /**
+            The script language being used. Currently supported values are
+            <code>javascript</code> and <code>mvel</code>.
+
+            @member ejs.ScriptField
+            @param {String} language The language of the script.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (language) {
+        if (language == null) {
+          return script[fieldName].lang;
+        }
+      
+        script[fieldName].lang = language;
+        return this;
+      },
+
+      /**
+            Sets the script/code that will be used to perform the calculation.
+
+            @member ejs.ScriptField
+            @param {String} expression The script/code to use.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      script: function (expression) {
+        if (expression == null) {
+          return script[fieldName].script;
+        }
+      
+        script[fieldName].script = expression;
+        return this;
+      },
+
+      /**
+            Allows you to set script parameters to be used during the execution of the script.
+
+            @member ejs.ScriptField
+            @param {Object} oParams An object containing key/value pairs representing param name/value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (oParams) {
+        if (oParams == null) {
+          return script[fieldName].params;
+        }
+      
+        script[fieldName].params = oParams;
+        return this;
+      },
+
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.ScriptField
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(script);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.ScriptField
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'script field';
+      },
+      
+      /**
+            Retrieves the internal <code>script</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.ScriptField
+            @returns {String} returns this object's internal <code>facet</code> property.
+            */
+      _self: function () {
+        return script;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A Shape object that can be used in queries and filters that 
+    take a Shape.  Shape uses the GeoJSON format.</p>
+
+    <p>See http://www.geojson.org/</p>
+
+    @name ejs.Shape
+
+    @desc
+    <p>Defines a shape</p>
+
+    @param {String} type A valid shape type.
+    @param {Array} coords An valid coordinat definition for the given shape.
+
+    */
+  ejs.Shape = function (type, coords) {
+  
+    var 
+      shape = {},
+      validType = function (t) {
+        var valid = false;
+        if (t === 'point' || t === 'linestring' || t === 'polygon' || 
+          t === 'multipoint' || t === 'envelope' || t === 'multipolygon') {
+          valid = true;
+        }
+
+        return valid;
+      };
+    
+    type = type.toLowerCase();
+    if (validType(type)) {
+      shape.type = type;
+      shape.coordinates = coords;
+    }  
+  
+    return {
+
+      /**
+            Sets the shape type.  Can be set to one of:  point, linestring, polygon,
+            multipoint, envelope, or multipolygon.
+
+            @member ejs.Shape
+            @param {String} t a valid shape type.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (t) {
+        if (t == null) {
+          return shape.type;
+        }
+      
+        t = t.toLowerCase();
+        if (validType(t)) {
+          shape.type = t;
+        }
+      
+        return this;
+      },
+
+      /**
+            Sets the coordinates for the shape definition.  Note, the coordinates
+            are not validated in this api.  Please see GeoJSON and ElasticSearch
+            documentation for correct coordinate definitions.
+
+            @member ejs.Shape
+            @param {Array} c a valid coordinates definition for the shape.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      coordinates: function (c) {
+        if (c == null) {
+          return shape.coordinates;
+        }
+
+        shape.coordinates = c;
+        return this;
+      },
+        
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.Shape
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(shape);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+            
+            @member ejs.Shape
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'shape';
+      },
+      
+      /**
+            Retrieves the internal <code>script</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.Shape
+            @returns {String} returns this object's internal object representation.
+            */
+      _self: function () {
+        return shape;
+      }
+    };
+  };
+
+  /**
+    @class
+    <p>A Sort object that can be used in on the Request object to specify 
+    various types of sorting.</p>
+
+    <p>See http://www.elasticsearch.org/guide/reference/api/search/sort.html</p>
+
+    @name ejs.Sort
+
+    @desc
+    <p>Defines a sort value</p>
+
+    @param {String} fieldName The fieldName to sort against.  Defaults to _score
+      if not specified.
+    */
+  ejs.Sort = function (fieldName) {
+
+    // default to sorting against the documents score.
+    if (fieldName == null) {
+      fieldName = '_score';
+    }
+  
+    var sort = {},
+      key = fieldName, // defaults to field search
+      geo_key = '_geo_distance', // used when doing geo distance sort
+      script_key = '_script'; // used when doing script sort
+    
+    // defaults to a field sort
+    sort[key] = {};
+
+    return {
+
+      /**
+            Set's the field to sort on
+
+            @member ejs.Sort
+            @param {String} f The name of a field 
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      field: function (f) {
+        var oldValue = sort[key];
+      
+        if (f == null) {
+          return fieldName;
+        }
+    
+        delete sort[key];      
+        fieldName = f;
+        key = f;
+        sort[key] = oldValue;
+      
+        return this;
+      },
+
+      /**
+            Enables sorting based on a distance from a GeoPoint
+
+            @member ejs.Sort
+            @param {GeoPoint} point A valid GeoPoint object
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      geoDistance: function (point) {
+        var oldValue = sort[key];
+      
+        if (point == null) {
+          return sort[key][fieldName];
+        }
+    
+        if (!isGeoPoint(point)) {
+          throw new TypeError('Argument must be a GeoPoint');
+        }
+      
+        delete sort[key];
+        key = geo_key;
+        sort[key] = oldValue;
+        sort[key][fieldName] = point._self();
+      
+        return this;
+      },
+    
+      /**
+            Enables sorting based on a script.
+
+            @member ejs.Sort
+            @param {String} scriptCode The script code as a string
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      script: function (scriptCode) {
+        var oldValue = sort[key];
+      
+        if (scriptCode == null) {
+          return sort[key].script;
+        }
+      
+        delete sort[key];
+        key = script_key;
+        sort[key] = oldValue;
+        sort[key].script = scriptCode;
+      
+        return this;
+      },
+    
+      /**
+            Sets the sort order.  Valid values are:
+          
+            asc - for ascending order
+            desc - for descending order
+
+            Valid during sort types:  field, geo distance, and script
+          
+            @member ejs.Sort
+            @param {String} o The sort order as a string, asc or desc.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      order: function (o) {
+        if (o == null) {
+          return sort[key].order;
+        }
+    
+        o = o.toLowerCase();
+        if (o === 'asc' || o === 'desc') {
+          sort[key].order = o;  
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets the sort order to ascending (asc).  Same as calling
+            <code>order('asc')</code>.
+          
+            @member ejs.Sort
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      asc: function () {
+        sort[key].order = 'asc';
+        return this;
+      },
+      
+      /**
+            Sets the sort order to descending (desc).  Same as calling
+            <code>order('desc')</code>.
+          
+            @member ejs.Sort
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      desc: function () {
+        sort[key].order = 'desc';
+        return this;
+      },
+      
+      /**
+            Sets the order with a boolean value.  
+          
+            true = descending sort order
+            false = ascending sort order
+
+            Valid during sort types:  field, geo distance, and script
+          
+            @member ejs.Sort
+            @param {Boolean} trueFalse If sort should be in reverse order.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      reverse: function (trueFalse) {
+        if (trueFalse == null) {
+          return sort[key].reverse;
+        }
+    
+        sort[key].reverse = trueFalse;  
+        return this;
+      },
+    
+      /**
+            Sets the value to use for missing fields.  Valid values are:
+          
+            _last - to put documents with the field missing last
+            _first - to put documents with the field missing first
+            {String} - any string value to use as the sort value.
+
+            Valid during sort types:  field
+          
+            @member ejs.Sort
+            @param {String} m The value to use for documents with the field missing.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      missing: function (m) {
+        if (m == null) {
+          return sort[key].missing;
+        }
+    
+        sort[key].missing = m;  
+        return this;
+      },
+    
+      /**
+            Sets if the sort should ignore unmapped fields vs throwing an error.
+
+            Valid during sort types:  field
+          
+            @member ejs.Sort
+            @param {Boolean} trueFalse If sort should ignore unmapped fields.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      ignoreUnmapped: function (trueFalse) {
+        if (trueFalse == null) {
+          return sort[key].ignore_unmapped;
+        }
+    
+        sort[key].ignore_unmapped = trueFalse;  
+        return this;
+      },
+    
+      /**
+             Sets the distance unit.  Valid values are "mi" for miles or "km"
+             for kilometers. Defaults to "km".
+
+             Valid during sort types:  geo distance
+           
+             @member ejs.Sort
+             @param {Number} unit the unit of distance measure.
+             @returns {Object} returns <code>this</code> so that calls can be chained.
+             */
+      unit: function (unit) {
+        if (unit == null) {
+          return sort[key].unit;
+        }
+    
+        unit = unit.toLowerCase();
+        if (unit === 'mi' || unit === 'km') {
+          sort[key].unit = unit;
+        }
+      
+        return this;
+      },
+    
+      /**
+            If the lat/long points should be normalized to lie within their
+            respective normalized ranges.
+          
+            Normalized ranges are:
+            lon = -180 (exclusive) to 180 (inclusive) range
+            lat = -90 to 90 (both inclusive) range
+
+            Valid during sort types:  geo distance
+          
+            @member ejs.Sort
+            @param {String} trueFalse True if the coordinates should be normalized. False otherwise.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      normalize: function (trueFalse) {
+        if (trueFalse == null) {
+          return sort[key].normalize;
+        }
+
+        sort[key].normalize = trueFalse;
+        return this;
+      },
+    
+      /**
+            How to compute the distance. Can either be arc (better precision) 
+            or plane (faster). Defaults to arc.
+
+            Valid during sort types:  geo distance
+          
+            @member ejs.Sort
+            @param {String} type The execution type as a string.  
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      distanceType: function (type) {
+        if (type == null) {
+          return sort[key].distance_type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'arc' || type === 'plane') {
+          sort[key].distance_type = type;
+        }
+      
+        return this;
+      },
+    
+      /**
+            Sets parameters that will be applied to the script.  Overwrites 
+            any existing params.
+
+            Valid during sort types:  script
+          
+            @member ejs.Sort
+            @param {Object} p An object where the keys are the parameter name and 
+              values are the parameter value.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      params: function (p) {
+        if (p == null) {
+          return sort[key].params;
+        }
+  
+        sort[key].params = p;
+        return this;
+      },
+  
+      /**
+            Sets the script language.
+
+            Valid during sort types:  script
+          
+            @member ejs.Sort
+            @param {String} lang The script language, default mvel.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      lang: function (lang) {
+        if (lang == null) {
+          return sort[key].lang;
+        }
+
+        sort[key].lang = lang;
+        return this;
+      },
+    
+      /**
+            Sets the script sort type.  Valid values are:
+          
+            string - script return value is sorted as a string
+            number - script return value is sorted as a number
+
+            Valid during sort types:  script
+          
+            @member ejs.Sort
+            @param {String} type The sort type.  Either string or number.
+            @returns {Object} returns <code>this</code> so that calls can be chained.
+            */
+      type: function (type) {
+        if (type == null) {
+          return sort[key].type;
+        }
+
+        type = type.toLowerCase();
+        if (type === 'string' || type === 'number') {
+          sort[key].type = type;
+        }
+      
+        return this;
+      },
+    
+      /**
+            Allows you to serialize this object into a JSON encoded string.
+
+            @member ejs.Sort
+            @returns {String} returns this object as a serialized JSON string.
+            */
+      toString: function () {
+        return JSON.stringify(sort);
+      },
+
+      /**
+            The type of ejs object.  For internal use only.
+          
+            @member ejs.Sort
+            @returns {String} the type of object
+            */
+      _type: function () {
+        return 'sort';
+      },
+    
+      /**
+            Retrieves the internal <code>script</code> object. This is typically used by
+            internal API functions so use with caution.
+
+            @member ejs.Sort
+            @returns {String} returns this object's internal object representation.
+            */
+      _self: function () {
+        return sort;
+      }
+    };
+  };
+
+  // run in noConflict mode
+  ejs.noConflict = function () {
+    root.ejs = _ejs;
+    return this;
+  };
+  
+}).call(this);

Diff do ficheiro suprimidas por serem muito extensas
+ 3 - 0
common/lib/elastic.min.js


Diff do ficheiro suprimidas por serem muito extensas
+ 1 - 0
common/lib/jquery-1.8.0.min.js


Diff do ficheiro suprimidas por serem muito extensas
+ 31 - 0
common/lib/jquery.flot.js


+ 810 - 0
common/lib/jquery.flot.pie.js

@@ -0,0 +1,810 @@
+/* Flot plugin for rendering pie charts.
+
+Copyright (c) 2007-2012 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes that each series has a single data value, and that each
+value is a positive integer or zero.  Negative numbers don't make sense for a
+pie chart, and have unpredictable results.  The values do NOT need to be
+passed in as percentages; the plugin will calculate the total and per-slice
+percentages internally.
+
+* Created by Brian Medendorp
+
+* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
+
+The plugin supports these options:
+
+	series: {
+		pie: {
+			show: true/false
+			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
+			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
+			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
+			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
+			offset: {
+				top: integer value to move the pie up or down
+				left: integer value to move the pie left or right, or 'auto'
+			},
+			stroke: {
+				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
+				width: integer pixel width of the stroke
+			},
+			label: {
+				show: true/false, or 'auto'
+				formatter:  a user-defined function that modifies the text/style of the label text
+				radius: 0-1 for percentage of fullsize, or a specified pixel length
+				background: {
+					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
+					opacity: 0-1
+				},
+				threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
+			},
+			combine: {
+				threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
+				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
+				label: any text value of what the combined slice should be labeled
+			}
+			highlight: {
+				opacity: 0-1
+			}
+		}
+	}
+
+More detail and specific examples can be found in the included HTML file.
+
+*/
+
+(function($) {
+
+	function init(plot) {
+
+		var canvas = null,
+			canvasWidth = 0,
+			canvasHeight = 0,
+			target = null,
+			maxRadius = null,
+			centerLeft = null,
+			centerTop = null,
+			total = 0,
+			redraw = true,
+			redrawAttempts = 10,
+			shrink = 0.95,
+			legendWidth = 0,
+			processed = false,
+			raw = false,
+			ctx = null;
+
+		// interactive variables
+
+		var highlights = [];
+
+		// add hook to determine if pie plugin in enabled, and then perform necessary operations
+
+		plot.hooks.processOptions.push(checkPieEnabled);
+		plot.hooks.bindEvents.push(bindEvents);
+
+		// check to see if the pie plugin is enabled
+
+		function checkPieEnabled(plot, options) {
+			if (options.series.pie.show) {
+
+				//disable grid
+
+				options.grid.show = false;
+
+				// set labels.show
+
+				if (options.series.pie.label.show == "auto") {
+					if (options.legend.show) {
+						options.series.pie.label.show = false;
+					} else {
+						options.series.pie.label.show = true;
+					}
+				}
+
+				// set radius
+
+				if (options.series.pie.radius == "auto") {
+					if (options.series.pie.label.show) {
+						options.series.pie.radius = 3/4;
+					} else {
+						options.series.pie.radius = 1;
+					}
+				}
+
+				// ensure sane tilt
+
+				if (options.series.pie.tilt > 1) {
+					options.series.pie.tilt = 1;
+				} else if (options.series.pie.tilt < 0) {
+					options.series.pie.tilt = 0;
+				}
+
+				// add processData hook to do transformations on the data
+
+				plot.hooks.processDatapoints.push(processDatapoints);
+				plot.hooks.drawOverlay.push(drawOverlay);
+
+				//  draw hook
+
+				plot.hooks.draw.push(draw);
+			}
+		}
+
+		// bind hoverable events
+
+		function bindEvents(plot, eventHolder) {
+			var options = plot.getOptions();
+			if (options.series.pie.show) {
+				if (options.grid.hoverable) {
+					eventHolder.unbind("mousemove").mousemove(onMouseMove);
+				}
+				if (options.grid.clickable) {
+					eventHolder.unbind("click").click(onClick);
+				}
+			}
+		}
+
+		// debugging function that prints out an object
+
+		function alertObject(obj) {
+
+			var msg = "";
+
+			function traverse(obj, depth) {
+
+				if (!depth) {
+					depth = 0;
+				}
+
+				for (var i = 0; i < obj.length; ++i) {
+					for (var j = 0; j < depth; j++) {
+						msg += "\t";
+					}
+					if( typeof obj[i] == "object") {
+						msg += "" + i + ":\n";
+						traverse(obj[i], depth + 1);
+					} else {
+						msg += "" + i + ": " + obj[i] + "\n";
+					}
+				}
+			}
+
+			traverse(obj);
+			alert(msg);
+		}
+
+		function calcTotal(data) {
+			for (var i = 0; i < data.length; ++i) {
+				var item = parseFloat(data[i].data[0][1]);
+				if (item) {
+					total += item;
+				}
+			}
+		}
+
+		function processDatapoints(plot, series, data, datapoints) {
+			if (!processed)	{
+				processed = true;
+				canvas = plot.getCanvas();
+				target = $(canvas).parent();
+				options = plot.getOptions();
+				plot.setData(combine(plot.getData()));
+			}
+		}
+
+		function setupPie() {
+
+			legendWidth = target.children().filter(".legend").children().width() || 0;
+
+			// calculate maximum radius and center point
+
+			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
+			centerTop = canvasHeight / 2 + options.series.pie.offset.top;
+			centerLeft = canvasWidth / 2;
+
+			if (options.series.pie.offset.left == "auto") {
+				if (options.legend.position.match("w")) {
+					centerLeft += legendWidth / 2;
+				} else {
+					centerLeft -= legendWidth / 2;
+				}
+			} else {
+				centerLeft += options.series.pie.offset.left;
+			}
+
+			if (centerLeft < maxRadius) {
+				centerLeft = maxRadius;
+			} else if (centerLeft > canvasWidth - maxRadius) {
+				centerLeft = canvasWidth - maxRadius;
+			}
+		}
+
+		function fixData(data) {
+			for (var i = 0; i < data.length; ++i) {
+				if (typeof(data[i].data) == "number") {
+					data[i].data = [[1, data[i].data]];
+				} else if (typeof(data[i].data) == "undefined" || typeof(data[i].data[0]) == "undefined") {
+					if (typeof(data[i].data) != "undefined" && typeof(data[i].data.label) != "undefined") {
+						data[i].label = data[i].data.label; // fix weirdness coming from flot
+					}
+					data[i].data = [[1, 0]];
+				}
+			}
+			return data;
+		}
+
+		function combine(data) {
+
+			data = fixData(data);
+			calcTotal(data);
+
+			var combined = 0;
+			var numCombined = 0;
+			var color = options.series.pie.combine.color;
+			var newdata = [];
+
+			for (var i = 0; i < data.length; ++i) {
+
+				// make sure its a number
+
+				data[i].data[0][1] = parseFloat(data[i].data[0][1]);
+
+				if (!data[i].data[0][1]) {
+					data[i].data[0][1] = 0;
+				}
+
+				if (data[i].data[0][1] / total <= options.series.pie.combine.threshold) {
+					combined += data[i].data[0][1];
+					numCombined++;
+					if (!color) {
+						color = data[i].color;
+					}
+				} else {
+					newdata.push({
+						data: [[1, data[i].data[0][1]]],
+						color: data[i].color,
+						label: data[i].label,
+						angle: data[i].data[0][1] * Math.PI * 2 / total,
+						percent: data[i].data[0][1] / (total / 100)
+					});
+				}
+			}
+
+			if (numCombined > 0) {
+				newdata.push({
+					data: [[1, combined]],
+					color: color,
+					label: options.series.pie.combine.label,
+					angle: combined * Math.PI * 2 / total,
+					percent: combined / (total / 100)
+				});
+			}
+
+			return newdata;
+		}
+
+		function draw(plot, newCtx) {
+
+			if (!target) {
+				return; // if no series were passed
+			}
+
+			canvasWidth = plot.getPlaceholder().width();
+			canvasHeight = plot.getPlaceholder().height();
+
+			ctx = newCtx;
+			setupPie();
+
+			var slices = plot.getData();
+			var attempts = 0;
+
+			while (redraw && attempts<redrawAttempts) {
+				redraw = false;
+				if (attempts > 0) {
+					maxRadius *= shrink;
+				}
+				attempts += 1;
+				clear();
+				if (options.series.pie.tilt <= 0.8) {
+					drawShadow();
+				}
+				drawPie();
+			}
+
+			if (attempts >= redrawAttempts) {
+				clear();
+				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
+			}
+
+			// Reset the redraw flag on success, so the loop above can run
+			// again in the event of a resize or other update.
+			// TODO: We should remove this redraw system entirely!
+
+			redraw = true;
+
+			if (plot.setSeries && plot.insertLegend) {
+				plot.setSeries(slices);
+				plot.insertLegend();
+			}
+
+			// we're actually done at this point, just defining internal functions at this point
+
+			function clear() {
+				ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+				target.children().filter(".pieLabel, .pieLabelBackground").remove();
+			}
+
+			function drawShadow() {
+
+				var shadowLeft = options.series.pie.shadow.left;
+				var shadowTop = options.series.pie.shadow.top;
+				var edge = 10;
+				var alpha = options.series.pie.shadow.alpha;
+				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
+					return;	// shadow would be outside canvas, so don't draw it
+				}
+
+				ctx.save();
+				ctx.translate(shadowLeft,shadowTop);
+				ctx.globalAlpha = alpha;
+				ctx.fillStyle = "#000";
+
+				// center and rotate to starting position
+
+				ctx.translate(centerLeft,centerTop);
+				ctx.scale(1, options.series.pie.tilt);
+
+				//radius -= edge;
+
+				for (var i = 1; i <= edge; i++) {
+					ctx.beginPath();
+					ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
+					ctx.fill();
+					radius -= i;
+				}
+
+				ctx.restore();
+			}
+
+			function drawPie() {
+
+				var startAngle = Math.PI * options.series.pie.startAngle;
+				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+				// center and rotate to starting position
+
+				ctx.save();
+				ctx.translate(centerLeft,centerTop);
+				ctx.scale(1, options.series.pie.tilt);
+				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
+
+				// draw slices
+
+				ctx.save();
+				var currentAngle = startAngle;
+				for (var i = 0; i < slices.length; ++i) {
+					slices[i].startAngle = currentAngle;
+					drawSlice(slices[i].angle, slices[i].color, true);
+				}
+				ctx.restore();
+
+				// draw slice outlines
+
+				if (options.series.pie.stroke.width > 0) {
+					ctx.save();
+					ctx.lineWidth = options.series.pie.stroke.width;
+					currentAngle = startAngle;
+					for (var i = 0; i < slices.length; ++i) {
+						drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
+					}
+					ctx.restore();
+				}
+
+				// draw donut hole
+
+				drawDonutHole(ctx);
+
+				// draw labels
+
+				if (options.series.pie.label.show) {
+					drawLabels();
+				}
+
+				// restore to original state
+				ctx.restore();
+
+				function drawSlice(angle, color, fill) {
+
+					if (angle <= 0 || isNaN(angle)) {
+						return;
+					}
+
+					if (fill) {
+						ctx.fillStyle = color;
+					} else {
+						ctx.strokeStyle = color;
+						ctx.lineJoin = "round";
+					}
+
+					ctx.beginPath();
+					if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
+						ctx.moveTo(0, 0); // Center of the pie
+					}
+
+					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
+					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
+					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
+					ctx.closePath();
+					//ctx.rotate(angle); // This doesn't work properly in Opera
+					currentAngle += angle;
+
+					if (fill) {
+						ctx.fill();
+					} else {
+						ctx.stroke();
+					}
+				}
+
+				function drawLabels() {
+
+					var currentAngle = startAngle;
+					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
+
+					for (var i = 0; i < slices.length; ++i) {
+						if (slices[i].percent >= options.series.pie.label.threshold * 100) {
+							drawLabel(slices[i], currentAngle, i);
+						}
+						currentAngle += slices[i].angle;
+					}
+
+					function drawLabel(slice, startAngle, index) {
+						if (slice.data[0][1] == 0) {
+							return;
+						}
+
+						// format label text
+
+						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
+
+						if (lf) {
+							text = lf(slice.label, slice);
+						} else {
+							text = slice.label;
+						}
+
+						if (plf) {
+							text = plf(text, slice);
+						}
+
+						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
+						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
+						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
+
+						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
+						target.append(html);
+
+						var label = target.children("#pieLabel" + index);
+						var labelTop = (y - label.height() / 2);
+						var labelLeft = (x - label.width() / 2);
+
+						label.css("top", labelTop);
+						label.css("left", labelLeft);
+
+						// check to make sure that the label is not outside the canvas
+
+						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
+							redraw = true;
+						}
+
+						if (options.series.pie.label.background.opacity != 0) {
+
+							// put in the transparent background separately to avoid blended labels and label boxes
+
+							var c = options.series.pie.label.background.color;
+
+							if (c == null) {
+								c = slice.color;
+							}
+
+							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
+							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
+								.css("opacity", options.series.pie.label.background.opacity)
+								.insertBefore(label);
+						}
+					} // end individual label function
+				} // end drawLabels function
+			} // end drawPie function
+		} // end draw function
+
+		// Placed here because it needs to be accessed from multiple locations
+
+		function drawDonutHole(layer) {
+			if (options.series.pie.innerRadius > 0) {
+
+				// subtract the center
+
+				layer.save();
+				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
+				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
+				layer.beginPath();
+				layer.fillStyle = options.series.pie.stroke.color;
+				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+				layer.fill();
+				layer.closePath();
+				layer.restore();
+
+				// add inner stroke
+
+				layer.save();
+				layer.beginPath();
+				layer.strokeStyle = options.series.pie.stroke.color;
+				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+				layer.stroke();
+				layer.closePath();
+				layer.restore();
+
+				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
+			}
+		}
+
+		//-- Additional Interactive related functions --
+
+		function isPointInPoly(poly, pt) {
+			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
+				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
+				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
+				&& (c = !c);
+			return c;
+		}
+
+		function findNearbySlice(mouseX, mouseY) {
+
+			var slices = plot.getData(),
+				options = plot.getOptions(),
+				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
+				x, y;
+
+			for (var i = 0; i < slices.length; ++i) {
+
+				var s = slices[i];
+
+				if (s.pie.show) {
+
+					ctx.save();
+					ctx.beginPath();
+					ctx.moveTo(0, 0); // Center of the pie
+					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
+					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
+					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
+					ctx.closePath();
+					x = mouseX - centerLeft;
+					y = mouseY - centerTop;
+
+					if (ctx.isPointInPath) {
+						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
+							ctx.restore();
+							return {
+								datapoint: [s.percent, s.data],
+								dataIndex: 0,
+								series: s,
+								seriesIndex: i
+							};
+						}
+					} else {
+
+						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
+
+						var p1X = radius * Math.cos(s.startAngle),
+							p1Y = radius * Math.sin(s.startAngle),
+							p2X = radius * Math.cos(s.startAngle + s.angle / 4),
+							p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
+							p3X = radius * Math.cos(s.startAngle + s.angle / 2),
+							p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
+							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
+							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
+							p5X = radius * Math.cos(s.startAngle + s.angle),
+							p5Y = radius * Math.sin(s.startAngle + s.angle),
+							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
+							arrPoint = [x, y];
+
+						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
+
+						if (isPointInPoly(arrPoly, arrPoint)) {
+							ctx.restore();
+							return {
+								datapoint: [s.percent, s.data],
+								dataIndex: 0,
+								series: s,
+								seriesIndex: i
+							};
+						}
+					}
+
+					ctx.restore();
+				}
+			}
+
+			return null;
+		}
+
+		function onMouseMove(e) {
+			triggerClickHoverEvent("plothover", e);
+		}
+
+		function onClick(e) {
+			triggerClickHoverEvent("plotclick", e);
+		}
+
+		// trigger click or hover event (they send the same parameters so we share their code)
+
+		function triggerClickHoverEvent(eventname, e) {
+
+			var offset = plot.offset();
+			var canvasX = parseInt(e.pageX - offset.left);
+			var canvasY =  parseInt(e.pageY - offset.top);
+			var item = findNearbySlice(canvasX, canvasY);
+
+			if (options.grid.autoHighlight) {
+
+				// clear auto-highlights
+
+				for (var i = 0; i < highlights.length; ++i) {
+					var h = highlights[i];
+					if (h.auto == eventname && !(item && h.series == item.series)) {
+						unhighlight(h.series);
+					}
+				}
+			}
+
+			// highlight the slice
+
+			if (item) {
+				highlight(item.series, eventname);
+			}
+
+			// trigger any hover bind events
+
+			var pos = { pageX: e.pageX, pageY: e.pageY };
+			target.trigger(eventname, [pos, item]);
+		}
+
+		function highlight(s, auto) {
+			//if (typeof s == "number") {
+			//	s = series[s];
+			//}
+
+			var i = indexOfHighlight(s);
+
+			if (i == -1) {
+				highlights.push({ series: s, auto: auto });
+				plot.triggerRedrawOverlay();
+			} else if (!auto) {
+				highlights[i].auto = false;
+			}
+		}
+
+		function unhighlight(s) {
+			if (s == null) {
+				highlights = [];
+				plot.triggerRedrawOverlay();
+			}
+
+			//if (typeof s == "number") {
+			//	s = series[s];
+			//}
+
+			var i = indexOfHighlight(s);
+
+			if (i != -1) {
+				highlights.splice(i, 1);
+				plot.triggerRedrawOverlay();
+			}
+		}
+
+		function indexOfHighlight(s) {
+			for (var i = 0; i < highlights.length; ++i) {
+				var h = highlights[i];
+				if (h.series == s)
+					return i;
+			}
+			return -1;
+		}
+
+		function drawOverlay(plot, octx) {
+
+			var options = plot.getOptions();
+
+			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+			octx.save();
+			octx.translate(centerLeft, centerTop);
+			octx.scale(1, options.series.pie.tilt);
+
+			for (var i = 0; i < highlights.length; ++i) {
+				drawHighlight(highlights[i].series);
+			}
+
+			drawDonutHole(octx);
+
+			octx.restore();
+
+			function drawHighlight(series) {
+
+				if (series.angle <= 0 || isNaN(series.angle)) {
+					return;
+				}
+
+				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
+				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
+				octx.beginPath();
+				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
+					octx.moveTo(0, 0); // Center of the pie
+				}
+				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
+				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
+				octx.closePath();
+				octx.fill();
+			}
+		}
+	} // end init (plugin body)
+
+	// define pie specific options and their default values
+
+	var options = {
+		series: {
+			pie: {
+				show: false,
+				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
+				innerRadius: 0, /* for donut */
+				startAngle: 3/2,
+				tilt: 1,
+				shadow: {
+					left: 5,	// shadow left offset
+					top: 15,	// shadow top offset
+					alpha: 0.02	// shadow alpha
+				},
+				offset: {
+					top: 0,
+					left: "auto"
+				},
+				stroke: {
+					color: "#fff",
+					width: 1
+				},
+				label: {
+					show: "auto",
+					formatter: function(label, slice) {
+						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
+					},	// formatter function
+					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
+					background: {
+						color: null,
+						opacity: 0
+					},
+					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
+				},
+				combine: {
+					threshold: -1,	// percentage at which to combine little slices into one larger slice
+					color: null,	// color to give the new slice (auto-generated if null)
+					label: "Other"	// label to give the new slice
+				},
+				highlight: {
+					//color: "#fff",		// will add this functionality once parseColor is available
+					opacity: 0.5
+				}
+			}
+		}
+	};
+
+	$.plot.plugins.push({
+		init: init,
+		options: options,
+		name: "pie",
+		version: "1.1"
+	});
+
+})(jQuery);

+ 188 - 0
common/lib/jquery.flot.stack.js

@@ -0,0 +1,188 @@
+/* Flot plugin for stacking data sets rather than overlyaing them.
+
+Copyright (c) 2007-2012 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes the data is sorted on x (or y if stacking horizontally).
+For line charts, it is assumed that if a line has an undefined gap (from a
+null point), then the line above it should have the same gap - insert zeros
+instead of "null" if you want another behaviour. This also holds for the start
+and end of the chart. Note that stacking a mix of positive and negative values
+in most instances doesn't make sense (so it looks weird).
+
+Two or more series are stacked when their "stack" attribute is set to the same
+key (which can be any number or string or just "true"). To specify the default
+stack, you can set the stack option like this:
+
+	series: {
+		stack: null or true or key (number/string)
+	}
+
+You can also specify it for a single series, like this:
+
+	$.plot( $("#placeholder"), [{
+		data: [ ... ],
+		stack: true
+	}])
+
+The stacking order is determined by the order of the data series in the array
+(later series end up on top of the previous).
+
+Internally, the plugin modifies the datapoints in each series, adding an
+offset to the y value. For line series, extra data points are inserted through
+interpolation. If there's a second y value, it's also adjusted (e.g for bar
+charts or filled areas).
+
+*/
+
+(function ($) {
+    var options = {
+        series: { stack: null } // or number/string
+    };
+    
+    function init(plot) {
+        function findMatchingSeries(s, allseries) {
+            var res = null;
+            for (var i = 0; i < allseries.length; ++i) {
+                if (s == allseries[i])
+                    break;
+                
+                if (allseries[i].stack == s.stack)
+                    res = allseries[i];
+            }
+            
+            return res;
+        }
+        
+        function stackData(plot, s, datapoints) {
+            if (s.stack == null)
+                return;
+
+            var other = findMatchingSeries(s, plot.getData());
+            if (!other)
+                return;
+
+            var ps = datapoints.pointsize,
+                points = datapoints.points,
+                otherps = other.datapoints.pointsize,
+                otherpoints = other.datapoints.points,
+                newpoints = [],
+                px, py, intery, qx, qy, bottom,
+                withlines = s.lines.show,
+                horizontal = s.bars.horizontal,
+                withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
+                withsteps = withlines && s.lines.steps,
+                fromgap = true,
+                keyOffset = horizontal ? 1 : 0,
+                accumulateOffset = horizontal ? 0 : 1,
+                i = 0, j = 0, l, m;
+
+            while (true) {
+                if (i >= points.length)
+                    break;
+
+                l = newpoints.length;
+
+                if (points[i] == null) {
+                    // copy gaps
+                    for (m = 0; m < ps; ++m)
+                        newpoints.push(points[i + m]);
+                    i += ps;
+                }
+                else if (j >= otherpoints.length) {
+                    // for lines, we can't use the rest of the points
+                    if (!withlines) {
+                        for (m = 0; m < ps; ++m)
+                            newpoints.push(points[i + m]);
+                    }
+                    i += ps;
+                }
+                else if (otherpoints[j] == null) {
+                    // oops, got a gap
+                    for (m = 0; m < ps; ++m)
+                        newpoints.push(null);
+                    fromgap = true;
+                    j += otherps;
+                }
+                else {
+                    // cases where we actually got two points
+                    px = points[i + keyOffset];
+                    py = points[i + accumulateOffset];
+                    qx = otherpoints[j + keyOffset];
+                    qy = otherpoints[j + accumulateOffset];
+                    bottom = 0;
+
+                    if (px == qx) {
+                        for (m = 0; m < ps; ++m)
+                            newpoints.push(points[i + m]);
+
+                        newpoints[l + accumulateOffset] += qy;
+                        bottom = qy;
+                        
+                        i += ps;
+                        j += otherps;
+                    }
+                    else if (px > qx) {
+                        // we got past point below, might need to
+                        // insert interpolated extra point
+                        if (withlines && i > 0 && points[i - ps] != null) {
+                            intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
+                            newpoints.push(qx);
+                            newpoints.push(intery + qy);
+                            for (m = 2; m < ps; ++m)
+                                newpoints.push(points[i + m]);
+                            bottom = qy; 
+                        }
+
+                        j += otherps;
+                    }
+                    else { // px < qx
+                        if (fromgap && withlines) {
+                            // if we come from a gap, we just skip this point
+                            i += ps;
+                            continue;
+                        }
+                            
+                        for (m = 0; m < ps; ++m)
+                            newpoints.push(points[i + m]);
+                        
+                        // we might be able to interpolate a point below,
+                        // this can give us a better y
+                        if (withlines && j > 0 && otherpoints[j - otherps] != null)
+                            bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
+
+                        newpoints[l + accumulateOffset] += bottom;
+                        
+                        i += ps;
+                    }
+
+                    fromgap = false;
+                    
+                    if (l != newpoints.length && withbottom)
+                        newpoints[l + 2] += bottom;
+                }
+
+                // maintain the line steps invariant
+                if (withsteps && l != newpoints.length && l > 0
+                    && newpoints[l] != null
+                    && newpoints[l] != newpoints[l - ps]
+                    && newpoints[l + 1] != newpoints[l - ps + 1]) {
+                    for (m = 0; m < ps; ++m)
+                        newpoints[l + ps + m] = newpoints[l + m];
+                    newpoints[l + 1] = newpoints[l - ps + 1];
+                }
+            }
+
+            datapoints.points = newpoints;
+        }
+        
+        plot.hooks.processDatapoints.push(stackData);
+    }
+    
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'stack',
+        version: '1.2'
+    });
+})(jQuery);

+ 417 - 0
common/lib/jquery.flot.time.js

@@ -0,0 +1,417 @@
+/* Pretty handling of time axes.
+
+Copyright (c) 2007-2012 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Set axis.mode to "time" to enable. See the section "Time series data" in
+API.txt for details.
+
+*/
+
+(function($) {
+
+	var options = {};
+
+	// round to nearby lower multiple of base
+
+	function floorInBase(n, base) {
+		return base * Math.floor(n / base);
+	}
+
+	// Returns a string with the date d formatted according to fmt.
+	// A subset of the Open Group's strftime format is supported.
+
+	function formatDate(d, fmt, monthNames, dayNames) {
+
+		if (typeof d.strftime == "function") {
+			return d.strftime(fmt);
+		}
+
+		var leftPad = function(n, pad) {
+			n = "" + n;
+			pad = "" + (pad == null ? "0" : pad);
+			return n.length == 1 ? pad + n : n;
+		};
+
+		var r = [];
+		var escape = false;
+		var hours = d.getHours();
+		var isAM = hours < 12;
+
+		if (monthNames == null) {
+			monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+		}
+
+		if (dayNames == null) {
+			dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+		}
+
+		var hours12;
+
+		if (hours > 12) {
+			hours12 = hours - 12;
+		} else if (hours == 0) {
+			hours12 = 12;
+		} else {
+			hours12 = hours;
+		}
+
+		for (var i = 0; i < fmt.length; ++i) {
+
+			var c = fmt.charAt(i);
+
+			if (escape) {
+				switch (c) {
+					case 'a': c = "" + dayNames[d.getDay()]; break;
+					case 'b': c = "" + monthNames[d.getMonth()]; break;
+					case 'd': c = leftPad(d.getDate()); break;
+					case 'e': c = leftPad(d.getDate(), " "); break;
+					case 'H': c = leftPad(hours); break;
+					case 'I': c = leftPad(hours12); break;
+					case 'l': c = leftPad(hours12, " "); break;
+					case 'm': c = leftPad(d.getMonth() + 1); break;
+					case 'M': c = leftPad(d.getMinutes()); break;
+					// quarters not in Open Group's strftime specification
+					case 'q':
+						c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
+					case 'S': c = leftPad(d.getSeconds()); break;
+					case 'y': c = leftPad(d.getFullYear() % 100); break;
+					case 'Y': c = "" + d.getFullYear(); break;
+					case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
+					case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
+					case 'w': c = "" + d.getDay(); break;
+				}
+				r.push(c);
+				escape = false;
+			} else {
+				if (c == "%") {
+					escape = true;
+				} else {
+					r.push(c);
+				}
+			}
+		}
+
+		return r.join("");
+	}
+
+	// To have a consistent view of time-based data independent of which time
+	// zone the client happens to be in we need a date-like object independent
+	// of time zones.  This is done through a wrapper that only calls the UTC
+	// versions of the accessor methods.
+
+	function makeUtcWrapper(d) {
+
+		function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
+			sourceObj[sourceMethod] = function() {
+				return targetObj[targetMethod].apply(targetObj, arguments);
+			};
+		};
+
+		var utc = {
+			date: d
+		};
+
+		// support strftime, if found
+
+		if (d.strftime != undefined) {
+			addProxyMethod(utc, "strftime", d, "strftime");
+		}
+
+		addProxyMethod(utc, "getTime", d, "getTime");
+		addProxyMethod(utc, "setTime", d, "setTime");
+
+		var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
+
+		for (var p = 0; p < props.length; p++) {
+			addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
+			addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
+		}
+
+		return utc;
+	};
+
+	// select time zone strategy.  This returns a date-like object tied to the
+	// desired timezone
+
+	function dateGenerator(ts, opts) {
+		if (opts.timezone == "browser") {
+			return new Date(ts);
+		} else if (!opts.timezone || opts.timezone == "utc") {
+			return makeUtcWrapper(new Date(ts));
+		} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
+			var d = new timezoneJS.Date();
+			// timezone-js is fickle, so be sure to set the time zone before
+			// setting the time.
+			d.setTimezone(opts.timezone);
+			d.setTime(ts);
+			return d;
+		} else {
+			return makeUtcWrapper(new Date(ts));
+		}
+	}
+	
+	// map of app. size of time units in milliseconds
+
+	var timeUnitSize = {
+		"second": 1000,
+		"minute": 60 * 1000,
+		"hour": 60 * 60 * 1000,
+		"day": 24 * 60 * 60 * 1000,
+		"month": 30 * 24 * 60 * 60 * 1000,
+		"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
+		"year": 365.2425 * 24 * 60 * 60 * 1000
+	};
+
+	// the allowed tick sizes, after 1 year we use
+	// an integer algorithm
+
+	var baseSpec = [
+		[1, "second"], [2, "second"], [5, "second"], [10, "second"],
+		[30, "second"], 
+		[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+		[30, "minute"], 
+		[1, "hour"], [2, "hour"], [4, "hour"],
+		[8, "hour"], [12, "hour"],
+		[1, "day"], [2, "day"], [3, "day"],
+		[0.25, "month"], [0.5, "month"], [1, "month"],
+		[2, "month"]
+	];
+
+	// we don't know which variant(s) we'll need yet, but generating both is
+	// cheap
+
+	var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
+		[1, "year"]]);
+	var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
+		[1, "year"]]);
+
+	function init(plot) {
+		plot.hooks.processDatapoints.push(function (plot, series, datapoints) {
+			$.each(plot.getAxes(), function(axisName, axis) {
+
+				var opts = axis.options;
+
+				if (opts.mode == "time") {
+					axis.tickGenerator = function(axis) {
+
+						var ticks = [];
+						var d = dateGenerator(axis.min, opts);
+						var minSize = 0;
+
+						// make quarter use a possibility if quarters are
+						// mentioned in either of these options
+
+						var spec = (opts.tickSize && opts.tickSize[1] ===
+							"quarter") ||
+							(opts.minTickSize && opts.minTickSize[1] ===
+							"quarter") ? specQuarters : specMonths;
+
+						if (opts.minTickSize != null) {
+							if (typeof opts.tickSize == "number") {
+								minSize = opts.tickSize;
+							} else {
+								minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
+							}
+						}
+
+						for (var i = 0; i < spec.length - 1; ++i) {
+							if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+											  + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
+								&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+								break;
+							}
+						}
+
+						var size = spec[i][0];
+						var unit = spec[i][1];
+
+						// special-case the possibility of several years
+
+						if (unit == "year") {
+
+							// if given a minTickSize in years, just use it,
+							// ensuring that it's an integer
+
+							if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
+								size = Math.floor(opts.minTickSize[0]);
+							} else {
+
+								var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
+								var norm = (axis.delta / timeUnitSize.year) / magn;
+
+								if (norm < 1.5) {
+									size = 1;
+								} else if (norm < 3) {
+									size = 2;
+								} else if (norm < 7.5) {
+									size = 5;
+								} else {
+									size = 10;
+								}
+
+								size *= magn;
+							}
+
+							// minimum size for years is 1
+
+							if (size < 1) {
+								size = 1;
+							}
+						}
+
+						axis.tickSize = opts.tickSize || [size, unit];
+						var tickSize = axis.tickSize[0];
+						unit = axis.tickSize[1];
+
+						var step = tickSize * timeUnitSize[unit];
+
+						if (unit == "second") {
+							d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+						} else if (unit == "minute") {
+							d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+						} else if (unit == "hour") {
+							d.setHours(floorInBase(d.getHours(), tickSize));
+						} else if (unit == "month") {
+							d.setMonth(floorInBase(d.getMonth(), tickSize));
+						} else if (unit == "quarter") {
+							d.setMonth(3 * floorInBase(d.getMonth() / 3,
+								tickSize));
+						} else if (unit == "year") {
+							d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+						}
+
+						// reset smaller components
+
+						d.setMilliseconds(0);
+
+						if (step >= timeUnitSize.minute) {
+							d.setSeconds(0);
+						} else if (step >= timeUnitSize.hour) {
+							d.setMinutes(0);
+						} else if (step >= timeUnitSize.day) {
+							d.setHours(0);
+						} else if (step >= timeUnitSize.day * 4) {
+							d.setDate(1);
+						} else if (step >= timeUnitSize.month * 2) {
+							d.setMonth(floorInBase(d.getMonth(), 3));
+						} else if (step >= timeUnitSize.quarter * 2) {
+							d.setMonth(floorInBase(d.getMonth(), 6));
+						} else if (step >= timeUnitSize.year) {
+							d.setMonth(0);
+						}
+
+						var carry = 0;
+						var v = Number.NaN;
+						var prev;
+
+						do {
+
+							prev = v;
+							v = d.getTime();
+							ticks.push(v);
+
+							if (unit == "month" || unit == "quarter") {
+								if (tickSize < 1) {
+
+									// a bit complicated - we'll divide the
+									// month/quarter up but we need to take
+									// care of fractions so we don't end up in
+									// the middle of a day
+
+									d.setDate(1);
+									var start = d.getTime();
+									d.setMonth(d.getMonth() +
+										(unit == "quarter" ? 3 : 1));
+									var end = d.getTime();
+									d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+									carry = d.getHours();
+									d.setHours(0);
+								} else {
+									d.setMonth(d.getMonth() +
+										tickSize * (unit == "quarter" ? 3 : 1));
+								}
+							} else if (unit == "year") {
+								d.setFullYear(d.getFullYear() + tickSize);
+							} else {
+								d.setTime(v + step);
+							}
+						} while (v < axis.max && v != prev);
+
+						return ticks;
+					};
+
+					axis.tickFormatter = function (v, axis) {
+
+						var d = dateGenerator(v, axis.options);
+
+						// first check global format
+
+						if (opts.timeformat != null) {
+							return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
+						}
+
+						// possibly use quarters if quarters are mentioned in
+						// any of these places
+
+						var useQuarters = (axis.options.tickSize &&
+								axis.options.tickSize[1] == "quarter") ||
+							(axis.options.minTickSize &&
+								axis.options.minTickSize[1] == "quarter");
+
+						var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+						var span = axis.max - axis.min;
+						var suffix = (opts.twelveHourClock) ? " %p" : "";
+						var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
+						var fmt;
+
+						if (t < timeUnitSize.minute) {
+							fmt = hourCode + ":%M:%S" + suffix;
+						} else if (t < timeUnitSize.day) {
+							if (span < 2 * timeUnitSize.day) {
+								fmt = hourCode + ":%M" + suffix;
+							} else {
+								fmt = "%b %d " + hourCode + ":%M" + suffix;
+							}
+						} else if (t < timeUnitSize.month) {
+							fmt = "%b %d";
+						} else if ((useQuarters && t < timeUnitSize.quarter) ||
+							(!useQuarters && t < timeUnitSize.year)) {
+							if (span < timeUnitSize.year) {
+								fmt = "%b";
+							} else {
+								fmt = "%b %Y";
+							}
+						} else if (useQuarters && t < timeUnitSize.year) {
+							if (span < timeUnitSize.year) {
+								fmt = "Q%q";
+							} else {
+								fmt = "Q%q %Y";
+							}
+						} else {
+							fmt = "%Y";
+						}
+
+						var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
+
+						return rt;
+					};
+				}
+			});
+		});
+	}
+
+	$.plot.plugins.push({
+		init: init,
+		options: options,
+		name: 'time',
+		version: '1.0'
+	});
+
+	// Time-axis support used to be in Flot core, which exposed the
+	// formatDate function on the plot object.  Various plugins depend
+	// on the function, so we need to re-expose it here.
+
+	$.plot.formatDate = formatDate;
+
+})(jQuery);

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
common/lib/json2.min.js


Diff do ficheiro suprimidas por serem muito extensas
+ 3 - 0
common/lib/modernizr-2.6.1.min.js


+ 31 - 0
common/lib/settings.js

@@ -0,0 +1,31 @@
+
+// To add a setting, you MUST define a default.
+var Settings = function (s) {
+  var _d = {
+    timespan      : '1h',
+    refresh       : 10000,
+    elasticsearch : 'localhost:9200',
+    perpage       : 50,
+    timezone      : 'user',
+    timeformat    : 'mm/dd HH:MM:ss',
+    timefield     : '@timestamp',
+    defaultfields : ['@message'],
+    operator      : 'OR',
+    exportdelim   : ',',
+    smartindex    : true,
+    indexpattern  : 'logstash-%Y.%m.%d',
+    indexlimit    : 150,
+    indexdefault  : 'logstash-*',
+    primaryfield  : '_all'
+  }
+
+  // This initializes a new hash on purpose, to avoid adding parameters to 
+  // kibanaconfig.js without providing sane defaults
+  var _s = {};
+  _.each(_d, function(v, k) {
+    _s[k] = typeof s[k] !== 'undefined' ? s[k]  : _d[k];
+  });
+
+  return _s;
+
+};

+ 440 - 0
common/lib/shared.js

@@ -0,0 +1,440 @@
+function get_object_fields(obj) {
+  var field_array = [];
+  obj = flatten_json(obj._source)
+  for (field in obj) {
+    field_array.push(field);
+  }
+  return field_array.sort();
+}
+
+function get_all_fields(json) {
+  var field_array = [];
+  var obj_fields;
+  for (hit in json.hits.hits) {
+    obj_fields = get_object_fields(json.hits.hits[hit]);
+    for (index in obj_fields) {
+      if (_.indexOf(field_array,obj_fields[index]) < 0) {
+        field_array.push(obj_fields[index]);
+      }
+    }
+  }
+  return field_array.sort();
+}
+
+function has_field(obj,field) {
+  var obj_fields = get_object_fields(obj);
+  if (_.inArray(obj_fields,field) < 0) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+// Retuns a sorted array with duplicates removed
+function array_unique(arr) {
+  var sorted_arr = arr.sort();
+  var results = [];
+  for (var i = 0; i <= arr.length - 1; i++) {
+    if (sorted_arr[i + 1] != sorted_arr[i]) {
+        results.push(sorted_arr[i]);
+    }
+  }
+  return results
+}
+
+function get_objids_with_field(json,field) {
+  var objid_array = [];
+  for (hit in json.hits.hits) {
+    if(has_field(json.hits.hits[hit],field)) {
+      objid_array.push(hit);
+    }
+  }
+  return objid_array;
+}
+
+function get_objids_with_field_value(json,field,value) {
+  var objid_array = [];
+  for (hit in json.hits.hits) {
+    var hit_obj = json.hits.hits[hit];
+    if(has_field(hit_obj,field)) {
+      var field_val = get_field_value(hit_obj,field,'raw')
+      if(_.isArray(field_val)) {
+        if(_.inArray(field_val,field) >= 0) {
+          objid_array.push(hit);
+        }
+      } else {
+        if(field_val == value) {
+          objid_array.push(hit);
+        }
+      }
+    } else {
+      if ( value == '')
+        objid_array.push(hit);
+    }
+  }
+  return objid_array;
+}
+
+function get_related_fields(json,field) {
+  var field_array = []
+  for (hit in json.hits.hits) {
+    var obj_fields = get_object_fields(json.hits.hits[hit])
+    if (_.inArray(obj_fields,field) >= 0) {
+      field_array.push.apply(field_array,obj_fields);
+    }
+  }
+  var counts = count_values_in_array(field_array);
+  return counts;
+}
+
+function recurse_field_dots(object,field) {
+  var value = null;
+  if (typeof object[field] != 'undefined')
+    value = object[field];
+  else if (nested = field.match(/(.*?)\.(.*)/))
+    if(typeof object[nested[1]] != 'undefined')
+      value = (typeof object[nested[1]][nested[2]] != 'undefined') ?
+        object[nested[1]][nested[2]] : recurse_field_dots(
+          object[nested[1]],nested[2]);
+
+  return value;
+}
+
+function get_field_value(object,field,opt) {
+  var value = recurse_field_dots(object['_source'],field);
+
+  if(value === null)
+    return ''
+  if(_.isArray(value))
+    if (opt == 'raw') {
+      return value;
+    }
+    else {
+        var complex = false;
+        _.each(value, function(el, index) {
+            if (typeof(el) == 'object') {
+                complex = true;
+            }
+        })
+        if (complex) {
+            return JSON.stringify(value, null, 4);
+        }
+        return value.toString();
+    }
+  if(typeof value === 'object' && value != null)
+    // Leaving this out for now
+    //return opt == 'raw' ? value : JSON.stringify(value,null,4)
+    return JSON.stringify(value,null,4)
+
+  return (value != null) ? value.toString() : '';
+}
+
+
+
+// Returns a big flat array of all values for a field
+function get_all_values_for_field(json,field) {
+  var field_array = [];
+  for (hit in json.hits.hits) {
+    var value = get_field_value(json.hits.hits[hit],field,'raw')
+    if(typeof value === 'object' && value != null) {
+      field_array.push.apply(field_array,value);
+    } else {
+      field_array.push(value);
+    }
+  }
+  return field_array;
+}
+
+// Takes a flat array of values and returns an array of arrays
+// reverse sorted with counts
+function count_values_in_array(array) {
+  var count = {};
+  _.each(array, function(){
+    var num = this; // Get number
+    count[num] = count[num]+1 || 1; // Increment counter for each value
+  });
+
+  var tuples = [];
+  for (var key in count) tuples.push([key, count[key]]);
+  tuples.sort(function(a, b) {
+    a = a[1];
+    b = b[1];
+    return a < b ? -1 : (a > b ? 1 : 0);
+  });
+
+  tuples.reverse();
+
+  var count_array = [];
+  for (var i = 0; i < tuples.length; i++) {
+    var key = tuples[i][0];
+    var value = tuples[i][1];
+    count_array.push([key,value])
+  }
+  return count_array;
+}
+
+function top_field_values(json,field,count) {
+  var result = count_values_in_array(get_all_values_for_field(json,field));
+  return result.slice(0,count)
+}
+
+ /**
+   * Calculate a graph interval
+   *
+   * from::           Date object containing the start time
+   * to::             Date object containing the finish time
+   * size::           Calculate to approximately this many bars
+   * user_interval::  User specified histogram interval
+   *
+   */
+function calculate_interval(from,to,size,user_interval) {
+  if(_.isObject(from))
+    from = from.getTime();
+  if(_.isObject(to))
+    to = to.getTime();
+  return user_interval == 0 ? round_interval((to - from)/size) : user_interval;
+}
+
+function get_bar_count(from,to,interval) {
+  return (to - from)/interval;
+}
+
+function round_interval (interval) {
+  switch (true) {
+    case (interval <= 500):       return 100;
+    case (interval <= 5000):      return 1000;
+    case (interval <= 7500):      return 5000;
+    case (interval <= 15000):     return 10000;
+    case (interval <= 45000):     return 30000;
+    case (interval <= 180000):    return 60000;
+    case (interval <= 450000):    return 300000;
+    case (interval <= 1200000):   return 600000;
+    case (interval <= 2700000):   return 1800000;
+    case (interval <= 7200000):   return 3600000;
+    case (interval <= 21600000):  return 10800000;
+    default:                      return 43200000;
+  }
+}
+
+function secondsToHms(seconds){
+    var numyears = Math.floor(seconds / 31536000);
+    if(numyears){
+        return numyears + 'y';
+    }
+    var numdays = Math.floor((seconds % 31536000) / 86400);
+    if(numdays){
+        return numdays + 'd';
+    }
+    var numhours = Math.floor(((seconds % 31536000) % 86400) / 3600);
+    if(numhours){
+        return numhours + 'h';
+    }
+    var numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60);
+    if(numminutes){
+        return numminutes + 'm';
+    }
+    var numseconds = (((seconds % 31536000) % 86400) % 3600) % 60;
+    if(numseconds){
+        return numseconds + 's';
+    }
+    return 'less then a second'; //'just now' //or other string you like;
+}
+
+function to_percent(number,outof) {
+  return Math.round((number/outof)*10000)/100 + "%";
+}
+
+function addslashes(str) {
+  str = str.replace(/\\/g, '\\\\');
+  str = str.replace(/\'/g, '\\\'');
+  str = str.replace(/\"/g, '\\"');
+  str = str.replace(/\0/g, '\\0');
+  return str;
+}
+
+// Create an ISO8601 compliant timestamp for ES
+//function ISODateString(unixtime) {
+  //var d = new Date(parseInt(unixtime));
+function ISODateString(d) {
+  if(is_int(d)) {
+    d = new Date(parseInt(d));
+  }
+
+  function pad(n) {
+    return n < 10 ? '0' + n : n
+  }
+  return d.getFullYear() + '-' +
+    pad(d.getMonth() + 1) + '-' +
+    pad(d.getDate()) + 'T' +
+    pad(d.getHours()) + ':' +
+    pad(d.getMinutes()) + ':' +
+    pad(d.getSeconds());
+}
+
+function pickDateString(d) {
+  return dateFormat(d,'yyyy-mm-dd HH:MM:ss')
+}
+
+function prettyDateString(d) {
+  d = new Date(parseInt(d));
+  d = utc_date_obj(d);
+  return dateFormat(d,window.time_format);
+}
+
+function utc_date_obj(d) {
+  return new Date(
+    d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(),  
+    d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(),
+    d.getUTCMilliseconds());
+}
+
+function local_date_obj(d) {
+  return new Date(Date.UTC(
+    d.getFullYear(), d.getMonth(), d.getDate(),  
+    d.getHours(), d.getMinutes(), d.getSeconds()));
+}
+
+function is_int(value) {
+  if ((parseFloat(value) == parseInt(value)) && !isNaN(value)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function interval_to_seconds(string) {
+  var matches = string.match(/(\d+)([Mwdhms])/);
+  switch (matches[2]) {
+    case 'M': return matches[1]*2592000;;
+    case 'w': return matches[1]*604800;;
+    case 'd': return matches[1]*86400;;
+    case 'h': return matches[1]*3600;;
+    case 'm': return matches[1]*60;;
+    case 's': return matches[1];
+  } 
+}
+
+function time_ago(string) {
+  return new Date(new Date().getTime() - (interval_to_seconds(string)*1000))
+}
+
+function flatten_json(object,root,array) {
+  if (typeof array === 'undefined')
+    var array = {};
+  if (typeof root === 'undefined')
+    var root = '';
+  for(var index in object) {
+    var obj = object[index]
+    var rootname = root.length == 0 ? index : root + '.' + index;
+    if(typeof obj == 'object' ) {
+      if(_.isArray(obj))
+        array[rootname] = typeof obj === 'undefined' ? null : obj.join(',');
+      else
+        flatten_json(obj,rootname,array)
+    } else {
+      array[rootname] = typeof obj === 'undefined' ? null : obj;
+    }
+  }
+  return sortObj(array);
+}
+
+function xmlEnt(value) {
+  if(_.isString(value)) {
+  var stg1 = value.replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/\r\n/g, '<br/>')
+    .replace(/\r/g, '<br/>')
+    .replace(/\n/g, '<br/>')
+    .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
+    .replace(/  /g, '&nbsp;&nbsp;')
+    .replace(/&lt;del&gt;/g, '<del>')
+    .replace(/&lt;\/del&gt;/g, '</del>');
+  return stg1
+  } else {
+    return value
+  }
+}
+
+function sortObj(arr) {
+  // Setup Arrays
+  var sortedKeys = new Array();
+  var sortedObj = {};
+
+  // Separate keys and sort them
+  for (var i in arr) {
+    sortedKeys.push(i);
+  }
+  sortedKeys.sort();
+
+  // Reconstruct sorted obj based on keys
+  for (var i in sortedKeys) {
+    sortedObj[sortedKeys[i]] = arr[sortedKeys[i]];
+  }
+  return sortedObj;
+}
+
+// WTF. Has to be a better way to do this. Hi Tyler.
+function int_to_tz(offset) {
+  var hour = offset / 1000 / 3600
+  var str = ""
+  if (hour == 0) {
+    str = "+0000"
+  }
+  if (hour < 0) {
+    if (hour > -10)
+      str = "-0" + (hour * -100)
+    else
+      str = "-" + (hour * -100)
+  }
+  if (hour > 0) {
+    if (hour < 10)
+      str = "+0" + (hour * 100)
+    else
+      str = "+" + (hour * 100)
+  }
+  str = str.substring(0,3) + ":" + str.substring(3);
+  return str
+}
+
+// Sets #hash, thus refreshing results
+function setHash(json) {
+  window.location.hash = encodeURIComponent(Base64.encode(JSON.stringify(json)));
+}
+
+// Add commas to numbers
+function addCommas(nStr) {
+  nStr += '';
+  var x = nStr.split('.');
+  var x1 = x[0];
+  var x2 = x.length > 1 ? '.' + x[1] : '';
+  var rgx = /(\d+)(\d{3})/;
+  while (rgx.test(x1)) {
+    x1 = x1.replace(rgx, '$1' + ',' + '$2');
+  }
+  return x1 + x2;
+}
+
+// Split up log spaceless strings
+// Str = string to split
+// num = number of letters between <wbr> tags
+function wbr(str, num) {
+  str = htmlEntities(str);
+  return str.replace(
+    RegExp("(@?\\w{" + num + "}|[:;,])([\\w\"'])([\\w@]*)", "g"),
+    function (all, text, char, trailer) {
+      if (/@KIBANA_\w+_(START|END)@/.test(all)) {
+        return text + char + trailer;
+      } else {
+        return text + "<del>&#8203;</del>" + char + trailer;
+      }
+    }
+  );
+}
+
+function htmlEntities(str) {
+    return String(str).replace(
+      /&/g, '&amp;').replace(
+      /</g, '&lt;').replace(
+      />/g, '&gt;').replace(
+      /"/g, '&quot;');
+}

+ 32 - 0
common/lib/underscore.min.js

@@ -0,0 +1,32 @@
+// Underscore.js 1.3.3
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
+c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
+g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
+c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
+a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
+c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
+a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
+function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
+(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
+j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
+0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
+e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
+i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
+1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
+i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
+g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
+return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
+c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
+function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
+b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
+b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
+function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
+u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
+b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
+this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

+ 43 - 0
config.js

@@ -0,0 +1,43 @@
+/*
+
+The settings before the break are the only ones that are currently implemented
+The remaining settings do nothing
+
+timespan:       Default timespan (eg 1d, 30d, 6h, 20m)
+refresh:        Milliseconds between auto refresh.
+timeformat:     Format for time in histograms (might go away)
+timefield:      Field to use for ISO8601 timestamps (might go away)
+indexpattern:   Timestamping pattern for time based indices, 
+
+NOTE: No timezone support yet, everything is in UTC at the moment.
+
+If you need to configure the default dashboard, please see dashboard.js
+
+shared.json contains an example sharable dashboard. Note the subtle differences
+between dashboard.js and shared.json. Once is a javascript object, the other is
+json.
+
+PLEASE SEE js/
+
+*/
+var config = new Settings(
+{
+    timespan:       '15m',
+    refresh:        30000,
+    elasticsearch:  'http://localhost:9200',
+    timeformat:     'mm/dd HH:MM:ss',
+    timefield:      '@timestamp', 
+    indexpattern:  '"logstash-"yyyy.mm.dd',
+    //indexpattern:   '"shakespeare"', 
+
+    defaultfields:  ['line_text'],
+    perpage:        50,
+    timezone:       'user',
+    operator:       'OR',
+    exportdelim:    ',',
+    smartindex:     true,
+    indexlimit:     150,
+    indexdefault:   'logstash-*',
+    primaryfield:   '_all'
+  }
+);

+ 54 - 0
dashboards.js

@@ -0,0 +1,54 @@
+var dashboards = 
+{
+  title: "Infinite Monkey Dashboard",
+  rows: {
+    row1: {
+      height: "200px",
+      panels: {
+        "Monkey Productivity": {
+          type    : "histogram",
+          span    : 8,
+          show    : ['lines','points'],
+          query   : "*",
+          label   : "Monkey lines of shakespeare",
+          color   : "#7BA4AF"
+        },
+        "Works of Shakespeare": {
+          type    : "pieterms",
+          legend  : true,
+          field   : "play_name",
+          span    : 4,
+          size    : 10,
+          query   : "*"
+        }
+      }
+    },
+    row2: {
+      height: "250px",
+      panels: {
+        "Royal Decrees": {
+          type    : "stackedquery",
+          span    : 4,
+          donut   : true,
+          queries : ['king','queen','duke'],
+        },
+        "Person: Thy vs Thou": {
+          type    : "piequery",
+          span    : 4,
+          donut   : true,
+          queries : ['thy','thou'],
+          colors  : ['#B07737','#85004B','#7BA4AF'],
+        },
+        "Main Characters": {
+          type    : "pieterms",
+          donut   : true,
+          legend  : true,
+          field   : "speaker",
+          span    : 4,
+          size    : 5,
+          query   : "*",
+        }
+      }
+    }
+  }
+};

+ 75 - 0
index.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7 ng-app:kibana-dash" lang="en" id="ng-app"> <![endif]-->
+<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8 ng-app:kibana-dash" lang="en" id="ng-app"> <![endif]-->
+<!--[if IE 8]>         <html class="no-js lt-ie9" lang="en" ng-app="kibana-dash"> <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js" lang="en" ng-app="kibana-dash"> <!--<![endif]-->
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="description" content="Search based application built using ElasticSearch, elastic.js, and Angular.js">
+  <meta name="viewport" content="width=device-width">
+
+  <title>Kibana Dashboard</title>
+
+  <link rel="stylesheet" href="/common/css/normalize.min.css">
+  <link rel="stylesheet" href="/common/css/main.css">
+  <link rel="stylesheet" href="/common/css/bootstrap.min.css">
+  <link rel="stylesheet" href="/common/css/bootstrap-responsive.min.css">
+  <link rel="stylesheet" href="/common/css/elasticjs.css">
+  <link rel="stylesheet" href="/common/css/datepicker.css">
+
+
+
+  <!-- project dependency libs -->
+  <script src="/common/lib/jquery-1.8.0.min.js"></script>
+  <script src="/common/lib/modernizr-2.6.1.min.js"></script>
+  <script src="/common/lib/underscore.min.js"></script>
+  <script src="/common/lib/angular.min.js"></script>
+  <script src="/common/lib/elastic.min.js"></script>
+  <script src="/common/lib/elastic-angular-client.min.js"></script>
+  <script src="/common/lib/dateformat.js"></script>
+  <script src="/common/lib/jquery.flot.js"></script>
+  <script src="/common/lib/jquery.flot.time.js"></script>
+  <script src="/common/lib/jquery.flot.stack.js"></script>
+  <script src="/common/lib/jquery.flot.pie.js"></script>
+  <script src="/common/lib/date.js"></script>
+  <script src="/common/lib/datepicker.js"></script>
+
+
+  <script src="/common/lib/settings.js"></script>
+  <script src="/config.js"></script>
+  <script src="/common/lib/shared.js"></script>
+  <script src="/dashboards.js"></script>
+
+    
+  <!-- project specific files -->
+  <script src="js/app.js"></script>
+  <script src="js/services.js"></script>
+  <script src="js/controllers.js"></script>
+  <script src="js/filters.js"></script>
+  <script src="js/directives.js"></script>
+  <script src="js/panels.js"></script>
+
+</head>
+
+<body ng-controller="SearchCtrl">
+  <div class="navbar navbar-fixed-top">
+    <div class="navbar-inner">
+      <div class="container-fluid">
+        <span class="brand">Kibana Dashboard</span>
+        <span class="brand"><small>Real time metrics</small></span>
+        <div  class="btn-group pull-right">
+          <button class="btn" ng-click="pause()"><i ng-class="{'icon-pause': playing,'icon-play': !playing}"></i></button>
+          <button class="btn" ng-repeat='timespan in time_options' ng-click="set_timespan(timespan)">{{timespan}}</button>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <div class="container-fluid">
+    <div class="row-fluid">
+      <div ng-view></div>
+    </div>
+  </div>
+</body>
+</html>

+ 21 - 0
js/app.js

@@ -0,0 +1,21 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+'use strict';
+
+/* Application level module which depends on filters, controllers, and services */
+angular.module('kibana-dash', [
+  'kibana-dash.controllers', 
+  'kibana-dash.filters', 
+  'kibana-dash.services', 
+  'kibana-dash.directives', 
+  'elasticjs.service',
+  'kibana-dash.panels'
+  ]).config(['$routeProvider', function($routeProvider) {
+    $routeProvider
+      .when('/dashboard', {
+        templateUrl: 'partials/dashboard.html' 
+      })
+      .otherwise({
+        redirectTo: '/dashboard'
+      });
+  }]);

+ 116 - 0
js/controllers.js

@@ -0,0 +1,116 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+'use strict';
+
+angular.module('kibana-dash.controllers', [])
+.controller('SearchCtrl', function($scope, $location, $http, $timeout, ejsResource) {
+
+
+  $scope.config = config;
+  $scope.dashboards = dashboards
+  $scope.timespan = config.timespan
+  $scope.from = time_ago($scope.timespan);
+  $scope.to = new Date();
+
+  $scope.time_options = ['5m','15m','1h','6h','12h','24h','2d','7d','30d'];
+
+  $scope.counter = 0;
+  $scope.playing = true;
+  $scope.play = function(){
+    $scope.counter++;
+    $scope.to = new Date();
+    $scope.from = time_ago($scope.timespan);
+    $scope.$root.$eval() 
+    mytimeout = $timeout($scope.play,config.refresh);
+  }
+
+  $scope.pause = function(){
+    if($scope.playing) {
+      $scope.playing = false;
+      $timeout.cancel(mytimeout);
+    } else {
+      $scope.playing = true;
+      mytimeout = $timeout($scope.play,config.refresh);
+    }
+  }
+  var mytimeout = $timeout($scope.play,config.refresh);
+
+
+
+  // If from/to to change, update index list
+  $scope.$watch(function() { 
+    return angular.toJson([$scope.from, $scope.to]) 
+  }, function(){
+    indices($scope.from,$scope.to).then(function (p) {
+      $scope.index = p.join();
+    });
+  });
+
+  // point to your ElasticSearch server
+  var ejs = $scope.ejs = ejsResource(config.elasticsearch);  
+
+  $scope.set_timespan = function(timespan) {
+    $scope.timespan = timespan;
+    $scope.from = time_ago($scope.timespan);
+  }
+
+  // returns a promise containing an array of all indices matching the index
+  // pattern that exist in a given range
+  function indices(from,to) {
+    var possible = [];
+    _.each(date_range(from,to.add_days(1)),function(d){
+      possible.push(d.format(config.indexpattern));
+    });
+
+    return all_indices().then(function(p) {
+      return _.intersection(p,possible);
+    })
+  };
+
+  // returns a promise containing an array of all indices in an elasticsearch
+  // cluster
+  function all_indices() {
+    var something = $http({
+      url: config.elasticsearch + "/_aliases",
+      method: "GET"
+    }).error(function(data, status, headers, config) {
+      $scope.error = status;
+    });
+
+    return something.then(function(p) {
+      var indices = [];
+      _.each(p.data, function(v,k) {
+        indices.push(k)
+      });
+      return indices;
+    });
+  }
+  
+});
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 96 - 0
js/directives.js

@@ -0,0 +1,96 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+'use strict';
+
+angular.module('kibana-dash.directives', [])
+.directive('panel', function($compile) {
+  return {
+    restrict: 'A',
+    compile: function(element, attrs) {
+      return function(scope, element, attrs) {
+        scope.$watch(function () {
+          return (attrs.panel && scope.index) ? true : false;
+        }, function (ready) {
+          if (ready) {
+            $compile("<div "+attrs.panel+" params={{panel}} style='height:{{row.height}}'></div>")(scope).appendTo(element);
+          }
+        });
+      }
+    }
+  }
+})
+.directive('upload', function(){
+  return {
+    restrict: 'A',
+    link: function(scope, elem, attrs) {
+      console.log(elem);
+      function file_selected(evt) {
+        var files = evt.target.files; // FileList object
+
+        // files is a FileList of File objects. List some properties.
+        var output = [];
+        for (var i = 0, f; f = files[i]; i++) {
+          var reader = new FileReader();
+          reader.onload = (function(theFile) {
+            return function(e) {
+              // Render thumbnail.
+              scope.dashboards = JSON.parse(e.target.result)
+              scope.$apply();
+            };
+          })(f);
+          reader.readAsText(f);
+        }
+      }
+
+      // Check for the various File API support.
+      if (window.File && window.FileReader && window.FileList && window.Blob) {
+        // Something
+        document.getElementById('upload').addEventListener('change', file_selected, false);
+      } else {
+        alert('Sorry, the HTML5 File APIs are not fully supported in this browser.');
+      }
+    }
+  }
+})
+.directive('datepicker', function(){
+  return {
+    restrict: 'A',
+    require: 'ngModel', 
+    link: function(scope, elem, attrs) {
+      elem.datepicker({
+        noDefault: false, // set this to true if you don't want the current date inserted if the value-attribute is empty
+        format: 'mm/dd/yyyy hh:ii:ss'
+      });
+    }
+  };
+})
+.directive('date', function(dateFilter) {
+  return {
+    require: 'ngModel',
+    link: function(scope, elm, attrs, ctrl) {
+
+      var dateFormat = attrs['date'] || 'yyyy-MM-dd HH:mm:ss';
+      var minDate = Date.parse(attrs['min']) || 0;
+      var maxDate = Date.parse(attrs['max']) || 9007199254740992;
+
+      ctrl.$parsers.unshift(function(viewValue) {
+        var parsedDateMilissec = Date.parse(viewValue);
+        if (parsedDateMilissec > 0) {
+          if (parsedDateMilissec >= minDate && parsedDateMilissec <= maxDate) {
+              ctrl.$setValidity('date', true);
+              return new Date(parsedDateMilissec);
+          }
+        }
+
+        // in all other cases it is invalid, return undefined (no model update)
+        ctrl.$setValidity('date', false);
+        return undefined;
+      });
+
+      ctrl.$formatters.unshift(function(modelValue) {
+        return dateFilter(modelValue, dateFormat);
+      });
+    }
+  };
+});
+

+ 12 - 0
js/filters.js

@@ -0,0 +1,12 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+'use strict';
+
+angular.module('kibana-dash.filters', [])
+  .filter('dateformat', ['dateformat', function(date) {
+    return function(date) {
+      console.log(date)
+      return "ahoy!"
+      //return String(date).replace(/\%VERSION\%/mg, version);
+    }
+  }]);

+ 644 - 0
js/panels.js

@@ -0,0 +1,644 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+'use strict';
+
+/* NOTE:  This is very much a preview, many things will change. In fact, this
+          file will probably go away
+*/
+
+/* 
+  METAPARAMETERS 
+
+  If you're implementing a panel, these are used by default. You need not handle
+  them in your directive.
+
+  span:   The grid is made up of N rows, however there are only 12 columns. Span
+          is a number, 1-12
+  type:   This is the name of your directive.  
+*/
+
+/*
+  Histogram
+
+  Draw a histogram of a single query
+
+  NOTE: This will likely be renamed or get a setting that allows for non-time
+  based keys. It may also be updated to allow multiple stacked or unstacked
+  queries. 
+
+  query:    query to execute
+  interval: Bucket size in the standard Nunit (eg 1d, 5m, 30s) Attempts to auto
+            scale itself based on timespan
+  color:    line/bar color.
+  show:     array of what to show, (eg ['bars','lines','points']) 
+*/
+
+/* 
+  Piequery  
+
+  Use a query facets to compare counts of for different queries, then show them
+  on a pie chart
+
+  queries:    An array of queries
+  donut:      Make a hole in the middle? 
+  tilt:       Tilt the pie in a 3dish way
+  legend:     Show it or not?
+  colors:     An array of colors to use for slices. These map 1-to-1 with the #
+              of queries in your queries array
+*/
+
+/* Pieterms
+
+  Use a terms facet to calculate the most popular terms for a field
+
+  query:    Query to perform the facet on
+  size:     Limit to this many terms
+  exclude:  An array of terms to exclude from the results
+  donut:      Make a hole in the middle? 
+  tilt:       Tilt the pie in a 3dish way
+  legend:     Show it or not?
+*/
+
+/* Stackedquery
+
+  Use date histograms to assemble stacked bar or line charts representing 
+  multple queries over time
+
+  queries:    An array of queries
+  interval:   Bucket size in the standard Nunit (eg 1d, 5m, 30s) Attempts to auto
+              scale itself based on timespan
+  colors:     An array of colors to use for slices. These map 1-to-1 with the #
+              of queries in your queries array
+  show:       array of what to show, (eg ['bars','lines','points']) 
+*/
+
+
+angular.module('kibana-dash.panels', [])
+.directive('histogram', function() {
+  return {
+    restrict: 'A',
+    link: function(scope, elem, attrs) {
+
+      // Specify defaults for ALL directives
+      var _d = {
+        query   : "*",
+        interval: secondsToHms(calculate_interval(scope.from,scope.to,40,0)/1000),
+        color   : "#27508C",
+        show    : ['bars']
+      }
+
+      // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
+      scope.$watch(function () {
+        return (attrs.params && scope.index) ? true : false;
+      }, function (ready) {
+        scope.ready = ready;
+        if(ready) {
+          scope.params = JSON.parse(attrs.params);
+          _.each(_d, function(v, k) {
+            scope.params[k] = _.isUndefined(scope.params[k]) 
+              ? _d[k] : scope.params[k];
+          });
+        }
+      });
+
+      // Also get the data if time frame changes.
+      // (REQUIRED IN EVERY PANEL)
+      scope.$watch(function() { 
+        return angular.toJson([scope.from, scope.to, scope.ready]) 
+      }, function(){
+        if(scope.ready)
+          if (_.isUndefined(attrs.params.interval))
+            scope.params.interval = secondsToHms(
+              calculate_interval(scope.from,scope.to,50,0)/1000),
+          get_data(scope,elem,attrs);
+      });
+
+      // Re-rending the panel if it is resized,
+      scope.$watch('data', function() {
+          render_panel(scope,elem,attrs);
+      });
+
+      // Or if the model changes
+      angular.element(window).bind('resize', function(){
+          render_panel(scope,elem,attrs);
+      });
+
+      // Function for getting data
+      function get_data(scope,elem,attrs) {
+        var params = scope.params;
+        var ejs = scope.ejs;
+        var request = ejs.Request().indices(scope.index);
+        
+        // Build the question part of the query
+        var query = ejs.FilteredQuery(
+          ejs.QueryStringQuery(params.query || '*'),
+          ejs.RangeFilter(config.timefield)
+            .from(scope.from)
+            .to(scope.to)
+            .cache(false)
+          );
+
+        // Then the insert into facet and make the request
+        var results = request
+          .facet(ejs.DateHistogramFacet('histogram')
+            .field(config.timefield)
+            .interval(params.interval)
+            .facetFilter(ejs.QueryFilter(query))
+          )
+          .doSearch();
+
+        // Populate scope when we have results
+        results.then(function(results) {
+          scope.hits = results.hits.total;
+          scope.data = results.facets.histogram.entries;
+        });
+      }
+
+      // Function for rendering panel
+      function render_panel(scope,elem,attrs) {
+        // Parse our params object
+        var params = scope.params;
+
+        // Determine format
+        var show = _.isUndefined(params.show) ? {
+            bars: true, lines: false, points: false
+          } : {
+            lines:  _.indexOf(params.show,'lines') < 0 ? false : true,
+            bars:   _.indexOf(params.show,'bars') < 0 ? false : true,
+            points: _.indexOf(params.show,'points') < 0 ? false : true,
+          }
+
+        // Push null values at beginning and end of timeframe
+        scope.graph = [
+          [scope.from.getTime(), null],[scope.to.getTime(), null]];
+
+        // Create FLOT value array 
+        _.each(scope.data, function(v, k) {
+          scope.graph.push([v['time'],v['count']])
+        });
+
+        // Set barwidth based on specified interval
+        var barwidth = interval_to_seconds(params.interval)*1000
+
+        // Populate element
+        $.plot(elem, [{
+          label: _.isUndefined(params.label) ? params.query: params.label, 
+          data: scope.graph
+        }], {
+          legend: { 
+            position: "nw", 
+            labelFormatter: function(label, series) {
+              return '<span class="legend">' + label + ' / ' + params.interval 
+                + '</span>';
+            }
+          },
+          series: {
+            lines:  { show: show.lines, fill: false },
+            bars:   { show: show.bars,  fill: 1, barWidth: barwidth/1.8 },
+            points: { show: show.points },
+            color: params.color,
+            shadowSize: 1
+          },
+          yaxis: { min: 0, color: "#000" },
+          xaxis: {
+            mode: "time",
+            timeformat: "%H:%M:%S<br>%m-%d",
+            label: "Datetime",
+            color: "#000",
+          },
+          grid: {
+            backgroundColor: '#fff',
+            borderWidth: 0,
+            borderColor: '#eee',
+            color: "#eee",
+            hoverable: true,
+          }
+        });
+        //elem.show();
+      }
+    }
+  };
+})
+.directive('pieterms', function() {
+  return {
+    restrict: 'A',
+    link: function(scope, elem, attrs) {
+
+      // Specify defaults for ALL directives
+      var _d = {
+        size    : 5,
+        query   : "*",
+        exclude : [],
+        donut   : false, 
+        tilt    : false,
+        legend  : true,
+      }
+
+      // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
+      scope.$watch(function () {
+        return (attrs.params && scope.index) ? true : false;
+      }, function (ready) {
+        scope.ready = ready;
+        if(ready) {
+          scope.params = JSON.parse(attrs.params);
+          _.each(_d, function(v, k) {
+            scope.params[k] = _.isUndefined(scope.params[k]) 
+              ? _d[k] : scope.params[k];
+          });
+        }
+      });
+
+      // Also get the data if time frame changes.
+      // (REQUIRED IN EVERY PANEL)
+      scope.$watch(function() { 
+        return angular.toJson([scope.from, scope.to, scope.ready]) 
+      }, function(){
+        if(scope.ready)
+          get_data(scope,elem,attrs);
+      });
+
+      // Re-rending the panel if it is resized,
+      scope.$watch('data', function() {
+          render_panel(scope,elem,attrs);
+      });
+
+      // Or if the model changes
+      angular.element(window).bind('resize', function(){
+          render_panel(scope,elem,attrs);
+      });
+
+      // Function for getting data
+      function get_data(scope,elem,attrs) {
+        var params = scope.params;
+        var ejs = scope.ejs;
+        var request = ejs.Request().indices(scope.index);
+        
+        // Build the question part of the query
+        var query = ejs.FilteredQuery(
+          ejs.QueryStringQuery(params.query || '*'),
+          ejs.RangeFilter(config.timefield)
+            .from(scope.from)
+            .to(scope.to)
+            .cache(false)
+          );
+
+        // Then the insert into facet and make the request
+        var results = request
+          .facet(ejs.TermsFacet('termpie')
+            .field(params.field)
+            .size(params['size'])
+            .exclude(params.exclude)
+            .facetFilter(ejs.QueryFilter(query))
+          )
+          .doSearch();
+
+        // Populate scope when we have results
+        results.then(function(results) {
+          scope.hits = results.hits.total;
+          scope.data = results.facets.termpie.terms;
+        });
+      }
+
+      // Function for rendering panel
+      function render_panel(scope,elem,attrs) {
+        // Parse our params object
+        var params = scope.params;
+
+        // Create graph array
+        scope.graph = [];
+        _.each(scope.data, function(v, k) {
+          if(!_.isUndefined(params.only) && _.indexOf(params.only,v['term']) < 0)
+            return
+
+          var point = {
+            label : v['term'],
+            data  : v['count']
+          }
+
+          if(!_.isUndefined(params.colors))
+            point.color = params.colors[_.indexOf(params.only,v['term'])] 
+
+          scope.graph.push(point)
+        });
+
+        var pie = {
+          series: {
+            pie: {
+              innerRadius: params.donut ? 0.4 : 0,
+              tilt: params.tilt ? 0.45 : 1,
+              radius: 1,
+              show: true,
+              combine: {
+                color: '#999',
+                label: 'The Rest'
+              },
+              label: { 
+                show: true,
+                radius: 2/3,
+                formatter: function(label, series){
+                  return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
+                    label+'<br/>'+Math.round(series.percent)+'%</div>';
+                },
+                threshold: 0.1 
+              }
+            }
+          },
+          //grid: { hoverable: true, clickable: true },
+          legend: { show: params.legend }
+        };
+
+        // Populate element
+        $.plot(elem, scope.graph, pie);
+        //elem.show();
+      }
+    }
+  };
+})
+.directive('piequery', function() {
+  return {
+    restrict: 'A',
+    link: function(scope, elem, attrs) {
+
+      // Specify defaults for ALL directives
+      var _d = {
+        queries : ["*"],
+        donut   : false, 
+        tilt    : false,
+        legend  : true,
+      }
+
+      // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
+      scope.$watch(function () {
+        return (attrs.params && scope.index) ? true : false;
+      }, function (ready) {
+        scope.ready = ready;
+        if(ready) {
+          scope.params = JSON.parse(attrs.params);
+          _.each(_d, function(v, k) {
+            scope.params[k] = _.isUndefined(scope.params[k]) 
+              ? _d[k] : scope.params[k];
+          });
+        }
+      });
+
+      // Also get the data if time frame changes.
+      // (REQUIRED IN EVERY PANEL)
+      scope.$watch(function() { 
+        return angular.toJson([scope.from, scope.to, scope.ready]) 
+      }, function(){
+        if(scope.ready)
+          get_data(scope,elem,attrs);
+      });
+
+      // Re-rending the panel if it is resized,
+      scope.$watch('data', function() {
+          render_panel(scope,elem,attrs);
+      });
+
+      // Or if the model changes
+      angular.element(window).bind('resize', function(){
+          render_panel(scope,elem,attrs);
+      });
+
+      // Function for getting data
+      function get_data(scope,elem,attrs) {
+        var params = scope.params;
+        var ejs = scope.ejs;
+        var request = ejs.Request().indices(scope.index);
+        
+
+        var queries = [];
+        // Build the question part of the query
+        _.each(params.queries, function(v) {
+          queries.push(ejs.FilteredQuery(
+            ejs.QueryStringQuery(v || '*'),
+            ejs.RangeFilter(config.timefield)
+              .from(scope.from)
+              .to(scope.to)
+              .cache(false))
+          )
+        });
+
+        _.each(queries, function(v) {
+          request = request.facet(ejs.QueryFacet(_.indexOf(queries,v))
+            .query(v)
+            .facetFilter(ejs.QueryFilter(v))
+          )
+        })
+        // Then the insert into facet and make the request
+        var results = request.doSearch();
+
+        // Populate scope when we have results
+        results.then(function(results) {
+          scope.hits = results.hits.total;
+          scope.data = results.facets;
+        });
+      }
+
+      // Function for rendering panel
+      function render_panel(scope,elem,attrs) {
+        // Parse our params object
+        var params = scope.params;
+
+        // Create graph array
+        scope.graph = [];
+        _.each(scope.data, function(v, k) {
+          var point = {
+            label : params.queries[k],
+            data  : v['count']
+          }
+          if(!_.isUndefined(params.colors))
+            point.color = params.colors[k%params.colors.length];
+          scope.graph.push(point)
+        });
+
+        // Populate element
+        $.plot(elem, scope.graph, {
+            series: {
+              pie: {
+                innerRadius: params.donut ? 0.4 : 0,
+                tilt: params.tilt ? 0.45 : 1,
+                radius: 1,
+                show: true,
+                combine: {
+                  color: '#999',
+                  label: 'The Rest'
+                },
+                label: { 
+                  show: true,
+                  radius: 2/3,
+                  formatter: function(label, series){
+                    return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
+                      label+'<br/>'+Math.round(series.percent)+'%</div>';
+                  },
+                  threshold: 0.1 
+                }
+              }
+            },
+            //grid: { hoverable: true, clickable: true },
+            legend: { show: params.legend }
+          });
+        //elem.show();
+      }
+    }
+  };
+})
+.directive('stackedquery', function() {
+  return {
+    restrict: 'A',
+    link: function(scope, elem, attrs) {
+
+      // Specify defaults for ALL directives
+      var _d = {
+        queries : ["*"],
+        interval: secondsToHms(calculate_interval(scope.from,scope.to,40,0)/1000),
+        colors  : ["#BF3030","#1D7373","#86B32D","#A98A21","#411F73"],
+        show    : ['bars']
+      }
+
+      // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
+      scope.$watch(function () {
+        return (attrs.params && scope.index) ? true : false;
+      }, function (ready) {
+        scope.ready = ready;
+        if(ready) {
+          scope.params = JSON.parse(attrs.params);
+          _.each(_d, function(v, k) {
+            scope.params[k] = _.isUndefined(scope.params[k]) 
+              ? _d[k] : scope.params[k];
+          });
+        }
+      });
+
+      // Also get the data if time frame changes.
+      // (REQUIRED IN EVERY PANEL)
+      scope.$watch(function() { 
+        return angular.toJson([scope.from, scope.to, scope.ready]) 
+      }, function(){
+        if(scope.ready)
+          if (_.isUndefined(attrs.params.interval))
+            scope.params.interval = secondsToHms(
+              calculate_interval(scope.from,scope.to,50,0)/1000),
+          get_data(scope,elem,attrs);
+      });
+
+      // Re-rending the panel if it is resized,
+      scope.$watch('data', function() {
+          render_panel(scope,elem,attrs);
+      });
+
+      // Or if the model changes
+      angular.element(window).bind('resize', function(){
+          render_panel(scope,elem,attrs);
+      });
+
+      // Function for getting data
+      function get_data(scope,elem,attrs) {
+        var params = scope.params;
+        var ejs = scope.ejs;
+        var request = ejs.Request().indices(scope.index);
+        
+        // Build the question part of the query
+        var queries = [];
+        _.each(params.queries, function(v) {
+          queries.push(ejs.FilteredQuery(
+            ejs.QueryStringQuery(v || '*'),
+            ejs.RangeFilter(config.timefield)
+              .from(scope.from)
+              .to(scope.to)
+              .cache(false))
+          )
+        });
+
+        // Build the facet part
+        _.each(queries, function(v) {
+          request = request
+            .facet(ejs.DateHistogramFacet(_.indexOf(queries,v))
+              .field(config.timefield)
+              .interval(params.interval)
+              .facetFilter(ejs.QueryFilter(v))
+            )
+        })
+
+        // Then run it
+        var results = request.doSearch();
+
+        // Populate scope when we have results
+        results.then(function(results) {
+          scope.hits = results.hits.total;
+          scope.data = results.facets;
+        });
+      }
+
+      // Function for rendering panel
+      function render_panel(scope,elem,attrs) {
+        // Parse our params object
+        var params = scope.params;
+
+        // Determine format
+        var show = _.isUndefined(params.show) ? {
+            bars: true, lines: false, points: false, fill: false
+          } : {
+            lines:  _.indexOf(params.show,'lines') < 0 ? false : true,
+            bars:   _.indexOf(params.show,'bars') < 0 ? false : true,
+            points: _.indexOf(params.show,'points') < 0 ? false : true,
+            fill:   _.indexOf(params.show,'fill') < 0 ? false : true
+          }
+
+        scope.graph = [];
+        // Push null values at beginning and end of timeframe
+        _.each(scope.data, function(v, k) {
+          var series = {};
+          var data = [[scope.from.getTime(), null]];
+          _.each(v.entries, function(v, k) {
+            data.push([v['time'],v['count']])
+          });
+          data.push([scope.to.getTime(), null])
+          series.data = {
+            label: params.queries[k], 
+            data: data, 
+            color: params.colors[k%params.colors.length]
+          };
+          scope.graph.push(series.data)
+        });
+
+        // Set barwidth based on specified interval
+        var barwidth = interval_to_seconds(params.interval)*1000
+
+        // Populate element
+        $.plot(elem, scope.graph, {
+          legend: { 
+            position: "nw", 
+            labelFormatter: function(label, series) {
+              return '<span class="legend">' + label + ' / ' + params.interval 
+                + '</span>';
+            }
+          },
+          series: {
+            stack:  0,
+            lines:  { show: show.lines, fill: show.fill },
+            bars:   { show: show.bars,  fill: 1, barWidth: barwidth/1.8 },
+            points: { show: show.points },
+            color: params.color,
+            shadowSize: 1
+          },
+          yaxis: { min: 0, color: "#000" },
+          xaxis: {
+            mode: "time",
+            timeformat: "%H:%M:%S<br>%m-%d",
+            label: "Datetime",
+            color: "#000",
+          },
+          grid: {
+            backgroundColor: '#fff',
+            borderWidth: 0,
+            borderColor: '#eee',
+            color: "#eee",
+            hoverable: true,
+          }
+        });
+        //elem.show();
+      }
+    }
+  };
+});

+ 5 - 0
js/services.js

@@ -0,0 +1,5 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+'use strict';
+
+angular.module('kibana-dash.services', []);

+ 14 - 0
partials/dashboard.html

@@ -0,0 +1,14 @@
+
+<div class="row-fluid" style="margin-top:50px">
+  <div class="row-fluid">
+    <div class="span8"><h2>{{dashboards.title}} <small>Last {{timespan}}</small></h2></div>
+    <div class="span4"><div><input type="file" id="upload" upload /></div></div>
+  </div>
+  <div class="row-fluid" ng-repeat="(row_name, row) in dashboards.rows" style="height:{{row.height}}">
+    <div ng-repeat="(panel_name, panel) in row.panels">
+      <div class="span{{panel.span}}" style="padding: 10px;height={{row.height}}" panel="{{panel.type}}" >
+        <h4>{{panel_name}}</h4>
+      </div>
+    </div>
+  </div>
+</div>

+ 8 - 0
partials/search.html

@@ -0,0 +1,8 @@
+
+<div class="row-fluid" style="margin-top:15%">
+    <form class="span4 offset4 form-inline" ng-submit="$parent.search()">
+        <input class="input-xlarge" ng-model="$parent.queryTerm" type="text" 
+            placeholder="Search" autofocus>
+        <button class="btn" type="submit">Search</button>
+    </form>
+</div>

+ 244 - 0
scripts/server.js

@@ -0,0 +1,244 @@
+#!/usr/bin/env node
+
+var util = require('util'),
+    http = require('http'),
+    fs = require('fs'),
+    url = require('url'),
+    events = require('events');
+
+var DEFAULT_PORT = 8000;
+
+function main(argv) {
+  new HttpServer({
+    'GET': createServlet(StaticServlet),
+    'HEAD': createServlet(StaticServlet)
+  }).start(Number(argv[2]) || DEFAULT_PORT);
+}
+
+function escapeHtml(value) {
+  return value.toString().
+    replace('<', '&lt;').
+    replace('>', '&gt;').
+    replace('"', '&quot;');
+}
+
+function createServlet(Class) {
+  var servlet = new Class();
+  return servlet.handleRequest.bind(servlet);
+}
+
+/**
+ * An Http server implementation that uses a map of methods to decide
+ * action routing.
+ *
+ * @param {Object} Map of method => Handler function
+ */
+function HttpServer(handlers) {
+  this.handlers = handlers;
+  this.server = http.createServer(this.handleRequest_.bind(this));
+}
+
+HttpServer.prototype.start = function(port) {
+  this.port = port;
+  this.server.listen(port);
+  util.puts('Http Server running at http://localhost:' + port + '/');
+};
+
+HttpServer.prototype.parseUrl_ = function(urlString) {
+  var parsed = url.parse(urlString);
+  parsed.pathname = url.resolve('/', parsed.pathname);
+  return url.parse(url.format(parsed), true);
+};
+
+HttpServer.prototype.handleRequest_ = function(req, res) {
+  var logEntry = req.method + ' ' + req.url;
+  if (req.headers['user-agent']) {
+    logEntry += ' ' + req.headers['user-agent'];
+  }
+  util.puts(logEntry);
+  req.url = this.parseUrl_(req.url);
+  var handler = this.handlers[req.method];
+  if (!handler) {
+    res.writeHead(501);
+    res.end();
+  } else {
+    handler.call(this, req, res);
+  }
+};
+
+/**
+ * Handles static content.
+ */
+function StaticServlet() {}
+
+StaticServlet.MimeMap = {
+  'txt': 'text/plain',
+  'html': 'text/html',
+  'css': 'text/css',
+  'xml': 'application/xml',
+  'json': 'application/json',
+  'js': 'application/javascript',
+  'jpg': 'image/jpeg',
+  'jpeg': 'image/jpeg',
+  'gif': 'image/gif',
+  'png': 'image/png',
+  'svg': 'image/svg+xml'
+};
+
+StaticServlet.prototype.handleRequest = function(req, res) {
+  var self = this;
+  var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
+    return String.fromCharCode(parseInt(hex, 16));
+  });
+  var parts = path.split('/');
+  if (parts[parts.length-1].charAt(0) === '.')
+    return self.sendForbidden_(req, res, path);
+  fs.stat(path, function(err, stat) {
+    if (err)
+      return self.sendMissing_(req, res, path);
+    if (stat.isDirectory())
+      return self.sendDirectory_(req, res, path);
+    return self.sendFile_(req, res, path);
+  });
+}
+
+StaticServlet.prototype.sendError_ = function(req, res, error) {
+  res.writeHead(500, {
+      'Content-Type': 'text/html'
+  });
+  res.write('<!doctype html>\n');
+  res.write('<title>Internal Server Error</title>\n');
+  res.write('<h1>Internal Server Error</h1>');
+  res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
+  util.puts('500 Internal Server Error');
+  util.puts(util.inspect(error));
+};
+
+StaticServlet.prototype.sendMissing_ = function(req, res, path) {
+  path = path.substring(1);
+  res.writeHead(404, {
+      'Content-Type': 'text/html'
+  });
+  res.write('<!doctype html>\n');
+  res.write('<title>404 Not Found</title>\n');
+  res.write('<h1>Not Found</h1>');
+  res.write(
+    '<p>The requested URL ' +
+    escapeHtml(path) +
+    ' was not found on this server.</p>'
+  );
+  res.end();
+  util.puts('404 Not Found: ' + path);
+};
+
+StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
+  path = path.substring(1);
+  res.writeHead(403, {
+      'Content-Type': 'text/html'
+  });
+  res.write('<!doctype html>\n');
+  res.write('<title>403 Forbidden</title>\n');
+  res.write('<h1>Forbidden</h1>');
+  res.write(
+    '<p>You do not have permission to access ' +
+    escapeHtml(path) + ' on this server.</p>'
+  );
+  res.end();
+  util.puts('403 Forbidden: ' + path);
+};
+
+StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
+  res.writeHead(301, {
+      'Content-Type': 'text/html',
+      'Location': redirectUrl
+  });
+  res.write('<!doctype html>\n');
+  res.write('<title>301 Moved Permanently</title>\n');
+  res.write('<h1>Moved Permanently</h1>');
+  res.write(
+    '<p>The document has moved <a href="' +
+    redirectUrl +
+    '">here</a>.</p>'
+  );
+  res.end();
+  util.puts('301 Moved Permanently: ' + redirectUrl);
+};
+
+StaticServlet.prototype.sendFile_ = function(req, res, path) {
+  var self = this;
+  var file = fs.createReadStream(path);
+  res.writeHead(200, {
+    'Content-Type': StaticServlet.
+      MimeMap[path.split('.').pop()] || 'text/plain'
+  });
+  if (req.method === 'HEAD') {
+    res.end();
+  } else {
+    file.on('data', res.write.bind(res));
+    file.on('close', function() {
+      res.end();
+    });
+    file.on('error', function(error) {
+      self.sendError_(req, res, error);
+    });
+  }
+};
+
+StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
+  var self = this;
+  if (path.match(/[^\/]$/)) {
+    req.url.pathname += '/';
+    var redirectUrl = url.format(url.parse(url.format(req.url)));
+    return self.sendRedirect_(req, res, redirectUrl);
+  }
+  fs.readdir(path, function(err, files) {
+    if (err)
+      return self.sendError_(req, res, error);
+
+    if (!files.length)
+      return self.writeDirectoryIndex_(req, res, path, []);
+
+    var remaining = files.length;
+    files.forEach(function(fileName, index) {
+      fs.stat(path + '/' + fileName, function(err, stat) {
+        if (err)
+          return self.sendError_(req, res, err);
+        if (stat.isDirectory()) {
+          files[index] = fileName + '/';
+        }
+        if (!(--remaining))
+          return self.writeDirectoryIndex_(req, res, path, files);
+      });
+    });
+  });
+};
+
+StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
+  path = path.substring(1);
+  res.writeHead(200, {
+    'Content-Type': 'text/html'
+  });
+  if (req.method === 'HEAD') {
+    res.end();
+    return;
+  }
+  res.write('<!doctype html>\n');
+  res.write('<title>' + escapeHtml(path) + '</title>\n');
+  res.write('<style>\n');
+  res.write('  ol { list-style-type: none; font-size: 1.2em; }\n');
+  res.write('</style>\n');
+  res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
+  res.write('<ol>');
+  files.forEach(function(fileName) {
+    if (fileName.charAt(0) !== '.') {
+      res.write('<li><a href="' +
+        escapeHtml(fileName) + '">' +
+        escapeHtml(fileName) + '</a></li>');
+    }
+  });
+  res.write('</ol>');
+  res.end();
+};
+
+// Must be last,
+main(process.argv);

+ 59 - 0
sharable.json

@@ -0,0 +1,59 @@
+{
+   "title":"Monkey Showdown",
+   "rows":{
+      "row2":{
+         "height":"270px",
+         "panels":{
+            "Hamlet vs macbeth":{
+               "type":"stackedquery",
+               "span":8,
+               "queries":[
+                  "play_name:Hamlet",
+                  "play_name:macbeth"
+               ],
+               "show": [ "lines", "fill" ]
+            },
+            "Hamlet vs Macbeth":{
+               "type":"piequery",
+               "span":4,
+               "donut":true,
+               "queries":[
+                  "play_name:Hamlet",
+                  "play_name:macbeth"
+               ],
+               "colors":[
+                  "#B07737",
+                  "#85004B",
+                  "#7BA4AF"
+               ],
+               "field":"@message"
+            }
+         }
+      },
+      "row3":{
+         "height":"130px",
+         "panels":{
+            "Hamlet's Lines":{
+               "type":"histogram",
+               "span":8,
+               "show":[
+                  "bars"
+               ],
+               "label":"lines",
+               "query":"speaker:HAMLET",
+               "color":"#4A8737"
+            },
+            "Speakers":{
+               "type":"pieterms",
+               "donut":false,
+               "tilt":false,
+               "legend":true,
+               "field":"speaker",
+               "span":4,
+               "size":6,
+               "query":"play_name:Hamlet OR play_name:macbeth"
+            }
+         }
+      }
+   }
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff