diff --git a/README.md b/README.md index f57a4dcde0b..739643c8e84 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,22 @@ kibana-dashboard ================ -Kibana Dashboard Preview \ No newline at end of file +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 diff --git a/common/.DS_Store b/common/.DS_Store new file mode 100644 index 00000000000..66267451b56 Binary files /dev/null and b/common/.DS_Store differ diff --git a/common/css/bootstrap-responsive.min.css b/common/css/bootstrap-responsive.min.css new file mode 100644 index 00000000000..2269019f189 --- /dev/null +++ b/common/css/bootstrap-responsive.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap Responsive v2.2.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/common/css/bootstrap.min.css b/common/css/bootstrap.min.css new file mode 100644 index 00000000000..43e16d72511 --- /dev/null +++ b/common/css/bootstrap.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap v2.2.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal;cursor:pointer}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info>label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn,.input-append select+.btn-group .btn,.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child,.table-bordered tfoot:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#333;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:2px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini{padding:1px 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #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(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;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(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;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(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;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(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;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(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;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(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar .btn+.btn,.btn-toolbar .btn-group+.btn,.btn-toolbar .btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu{font-size:14px}.btn-group>.btn-mini{font-size:11px}.btn-group>.btn-small{font-size:12px}.btn-group>.btn-large{font-size:16px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical .btn{display:block;float:none;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical .btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical .btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical .btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical .btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical .btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible;color:#777}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;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(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse{color:#999}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;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(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#ccc}.breadcrumb .active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:1px 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.popover .arrow,.popover .arrow:after{position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow:after{z-index:-1;content:""}.popover.top .arrow{bottom:-10px;left:50%;margin-left:-10px;border-top-color:#fff;border-width:10px 10px 0}.popover.top .arrow:after{bottom:-1px;left:-11px;border-top-color:rgba(0,0,0,0.25);border-width:11px 11px 0}.popover.right .arrow{top:50%;left:-10px;margin-top:-10px;border-right-color:#fff;border-width:10px 10px 10px 0}.popover.right .arrow:after{bottom:-11px;left:-1px;border-right-color:rgba(0,0,0,0.25);border-width:11px 11px 11px 0}.popover.bottom .arrow{top:-10px;left:50%;margin-left:-10px;border-bottom-color:#fff;border-width:0 10px 10px}.popover.bottom .arrow:after{top:-1px;left:-11px;border-bottom-color:rgba(0,0,0,0.25);border-width:0 11px 11px}.popover.left .arrow{top:50%;right:-10px;margin-top:-10px;border-left-color:#fff;border-width:10px 0 10px 10px}.popover.left .arrow:after{right:-1px;bottom:-11px;border-left-color:rgba(0,0,0,0.25);border-width:11px 0 11px 11px}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/common/css/datepicker.css b/common/css/datepicker.css new file mode 100644 index 00000000000..15268d94bb7 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/common/css/elasticjs.css b/common/css/elasticjs.css new file mode 100644 index 00000000000..a320f58e33c --- /dev/null +++ b/common/css/elasticjs.css @@ -0,0 +1,6 @@ +.axis path, +.axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} \ No newline at end of file diff --git a/common/css/main.css b/common/css/main.css new file mode 100644 index 00000000000..3d811d3dbe6 --- /dev/null +++ b/common/css/main.css @@ -0,0 +1,11 @@ +#events { + font-size: 12px; +} + +.legend { + color: #000; +} + +#upload { + display: inline-block; +} \ No newline at end of file diff --git a/common/css/normalize.min.css b/common/css/normalize.min.css new file mode 100644 index 00000000000..928e30601c3 --- /dev/null +++ b/common/css/normalize.min.css @@ -0,0 +1 @@ +/*! normalize.css v1.0.1 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.75em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} \ No newline at end of file diff --git a/common/img/glyphicons-halflings-white.png b/common/img/glyphicons-halflings-white.png new file mode 100644 index 00000000000..3bf6484a29d Binary files /dev/null and b/common/img/glyphicons-halflings-white.png differ diff --git a/common/img/glyphicons-halflings.png b/common/img/glyphicons-halflings.png new file mode 100644 index 00000000000..a9969993201 Binary files /dev/null and b/common/img/glyphicons-halflings.png differ diff --git a/common/lib/.DS_Store b/common/lib/.DS_Store new file mode 100644 index 00000000000..7b3aa0bae22 Binary files /dev/null and b/common/lib/.DS_Store differ diff --git a/common/lib/angular.min.js b/common/lib/angular.min.js new file mode 100644 index 00000000000..accc48e2f24 --- /dev/null +++ b/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=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;c2?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("
").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 
"+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 +-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;c4096&&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;f0&&(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("").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;lC.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("
"+Q(X)+"
").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;on.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("
"+Q(j)+"
").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;f0){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=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;f1;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;i7),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-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=j+l)for(var k=i.length-j,n=0;n0||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(;dl?(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":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)&&ah?(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;ex;)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@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/common/lib/bootstrap.min.js b/common/lib/bootstrap.min.js new file mode 100644 index 00000000000..55da8c39132 --- /dev/null +++ b/common/lib/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! +* Bootstrap.js by @fat & @mdo +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0]});if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(document).on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.detach().css({top:0,left:0,display:"block"}).insertAfter(this.$element),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.offset(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.detach(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);n[n.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
',trigger:"hover",title:"",delay:0,html:!1}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'

'})}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){var t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1},e.fn.typeahead.Constructor=t,e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery); \ No newline at end of file diff --git a/common/lib/date.js b/common/lib/date.js new file mode 100644 index 00000000000..77f498645c2 --- /dev/null +++ b/common/lib/date.js @@ -0,0 +1,104 @@ +/** + * Version: 1.0 Alpha-1 + * Build Date: 13-Nov-2007 + * Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved. + * License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. + * Website: http://www.datejs.com/ or http://www.coolite.com/datejs/ + */ +Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}}; +Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;idate)?1:(this=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;} +var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);} +if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);} +if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);} +if(x.hour||x.hours){this.addHours(x.hour||x.hours);} +if(x.month||x.months){this.addMonths(x.month||x.months);} +if(x.year||x.years){this.addYears(x.year||x.years);} +if(x.day||x.days){this.addDays(x.day||x.days);} +return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(valuemax){throw new RangeError(value+" is not a valid value for "+name+".");} +return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;} +if(!x.second&&x.second!==0){x.second=-1;} +if(!x.minute&&x.minute!==0){x.minute=-1;} +if(!x.hour&&x.hour!==0){x.hour=-1;} +if(!x.day&&x.day!==0){x.day=-1;} +if(!x.month&&x.month!==0){x.month=-1;} +if(!x.year&&x.year!==0){x.year=-1;} +if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());} +if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());} +if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());} +if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());} +if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());} +if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());} +if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());} +if(x.timezone){this.setTimezone(x.timezone);} +if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);} +return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;} +var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}} +return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();}; +Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;} +return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} +if(!last&&q[1].length===0){last=true;} +if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} +if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} +if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} +var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} +return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} +for(var i=0;i +* © 2011 Colin Snover +* 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 +* © 2011 Colin Snover +* 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 + * MIT license + * + * Includes enhancements by Scott Trenda + * and 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; +} \ No newline at end of file diff --git a/common/lib/datepicker.js b/common/lib/datepicker.js new file mode 100644 index 00000000000..671bbbf9eed --- /dev/null +++ b/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 = ''; + while (dowCnt < this.weekStart + 7) { + html += ''+this.days[(dowCnt++)%7]+''; + } + html += ''; + 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(''); + } + + 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('' + d.getDate() + ''); + + if (d.getDay() == this.weekEnd) { + html.push(''); + } + + 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: ''+ + ''+ + '←'+ + ''+ + '→'+ + ''+ + '', + contTemplate: '' + }; + DPGlobal.template = ''; + +}( 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"] + }); +}); \ No newline at end of file diff --git a/common/lib/elastic-angular-client.js b/common/lib/elastic-angular-client.js new file mode 100644 index 00000000000..8a3d660295c --- /dev/null +++ b/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; + }; +}]); diff --git a/common/lib/elastic-angular-client.min.js b/common/lib/elastic-angular-client.min.js new file mode 100644 index 00000000000..522b9e99d27 --- /dev/null +++ b/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}}]); \ No newline at end of file diff --git a/common/lib/elastic.js b/common/lib/elastic.js new file mode 100644 index 00000000000..7c2b6385f09 --- /dev/null +++ b/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 +

    The DateHistogram facet works with time-based values by building a histogram across time + intervals of the value field. Each value is rounded into an interval (or + placed in a bucket), and statistics are provided per interval/bucket (count and total).

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.DateHistogramFacet + + @desc +

    A facet which returns the N most frequent terms within a collection + or set of collections.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 year, month, week, day, hour, and minute. + @returns {Object} returns this 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 + preZone. Use preZone if possible. The + value is an offset from UTC. + + For example, to use EST you would set the value to -5. + + @member ejs.DateHistogramFacet + @param {Integer} tz An offset value from UTC. + @returns {Object} returns this 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 -5. + + @member ejs.DateHistogramFacet + @param {Integer} tz An offset value from UTC. + @returns {Object} returns this 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 interval to an + interval greater than a day. + + @member ejs.DateHistogramFacet + @param {Boolean} trueFalse A valid boolean value. + @returns {Object} returns this 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 -5. + + @member ejs.DateHistogramFacet + @param {Integer} tz An offset value from UTC. + @returns {Object} returns this 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 this 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 this 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 this 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 value 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 this 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 this 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 + javascript, groovy, and mvel. + + @member ejs.DateHistogramFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.DateHistogramFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.DateHistogramFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.DateHistogramFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.DateHistogramFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.DateHistogramFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.DateHistogramFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    The FilterFacet allows you to specify any valid Filter and + have the number of matching hits returned as the value.

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.FilterFacet + + @desc +

    A facet that return a count of the hits matching the given filter.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 { + + /** +

    Sets the filter to be used for this facet.

    + + @member ejs.FilterFacet + @param {Object} oFilter A valid Query object. + @returns {Object} returns this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.FilterFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.FilterFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.FilterFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.FilterFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.FilterFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.FilterFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    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).

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.GeoDistanceFacet + + @desc +

    A facet which provides information over a range of distances from a provided point.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 value 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 this 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 + javascript, groovy, and mvel. + + @member ejs.GeoDistanceFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.GeoDistanceFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.GeoDistanceFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.GeoDistanceFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.GeoDistanceFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.GeoDistanceFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.GeoDistanceFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    The histogram facet works with numeric data by building a histogram across intervals + of the field values. Each value is rounded into an interval (or placed in a + bucket), and statistics are provided per interval/bucket (count and total).

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.HistogramFacet + + @desc +

    A facet which returns the N most frequent terms within a collection + or set of collections.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 value 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 this 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 key 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 this 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 + javascript, groovy, and mvel. + + @member ejs.HistogramFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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 this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.HistogramFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.HistogramFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.HistogramFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.HistogramFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.HistogramFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.HistogramFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    The QueryFacet facet allows you to specify any valid Query and + have the number of matching hits returned as the value.

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.QueryFacet + + @desc +

    A facet that return a count of the hits matching the given query.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 { + + /** +

    Sets the query to be used for this facet.

    + + @member ejs.QueryFacet + @param {Object} oQuery A valid Query object. + @returns {Object} returns this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.QueryFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.QueryFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.QueryFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.QueryFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.QueryFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.QueryFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    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.

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.RangeFacet + + @desc +

    A facet which provides information over a range of numeric intervals.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 value 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 this 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 key 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 this 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 + javascript, groovy, and mvel. + + @member ejs.RangeFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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 Date). + @param {Number} to The upper bound of the range (can also be Date). + @returns {Object} returns this 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 Date). + @returns {Object} returns this 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 Date). + @returns {Object} returns this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.RangeFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.RangeFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.RangeFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.RangeFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.RangeFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.RangeFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    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.

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.StatisticalFacet + + @desc +

    A facet which returns statistical information about a numeric field

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 + javascript, groovy, and mvel. + + @member ejs.StatisticalFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this so that calls can be chained. + */ + params: function (oParams) { + if (oParams == null) { + return facet[name].statistical.params; + } + + facet[name].statistical.params = oParams; + return this; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.StatisticalFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.StatisticalFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.StatisticalFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.StatisticalFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.StatisticalFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.StatisticalFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    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.

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +

    For more information on faceted navigation, see this Wikipedia article on + Faceted ClassificationA facet which returns the N most frequent terms within a collection + or set of collections.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 authors 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 this 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 this 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; + }, + + /** +

    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 + out-of-memory errors.

    + + @member ejs.TermsFacet + @param {String} trueFalse true or false + @returns {Object} returns this 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; + }, + + /** +

    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.

    + + @member ejs.TermsFacet + @param {String || Array} exclude A single term to exclude or an + array of terms to exclude. + @returns {Object} returns this 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; + }, + + /** +

    Allows you to only include facet entries matching a specified regular expression.

    + + @member ejs.TermsFacet + @param {String} exp A valid regular expression. + @returns {Object} returns this so that calls can be chained. + */ + regex: function (exp) { + if (exp == null) { + return facet[name].terms.regex; + } + + facet[name].terms.regex = exp; + return this; + }, + + /** +

    Allows you to set the regular expression flags to be used + with the regex

    + + @member ejs.TermsFacet + @param {String} flags A valid regex flag - see Java Pattern API + @returns {Object} returns this 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 this 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 + javascript, groovy, and mvel. + + @member ejs.TermsFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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 this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.TermsFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.TermsFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.TermsFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.TermsFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.TermsFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet property. This is typically used by + internal API functions so use with caution.

    + + @member ejs.TermsFacet + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return facet; + } + }; + }; + + /** + @class +

    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 pivot table.

    + +

    Facets are similar to SQL GROUP BY statements but perform much + better. You can also construct several "groups" at once by simply + specifying multiple facets.

    + +
    +

    + Tip: + For more information on faceted navigation, see + this + Wikipedia article on Faceted Classification. +

    +
    + + @name ejs.TermStatsFacet + + @desc +

    A facet which computes statistical data based on an aggregate key.

    + + @param {String} name The name which be used to refer to this facet. For instance, + the facet itself might utilize a field named doc_authors. Setting + name to Authors 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 this 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 this 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 this 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 this 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; + }, + + /** +

    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 + out-of-memory errors.

    + + @member ejs.TermStatsFacet + @param {String} trueFalse true or false + @returns {Object} returns this 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 + javascript, groovy, and mvel. + + @member ejs.TermStatsFacet + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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. Higher settings could cause memory strain. + + @member ejs.TermStatsFacet + @param {Integer} facetSize The numer of facet entries to be returned. + @returns {Object} returns this 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 this 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; + }, + + /** +

    Allows you to reduce the documents used for computing facet results.

    + + @member ejs.TermStatsFacet + @param {Object} oFilter A valid Filter object. + @returns {Object} returns this 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; + }, + + /** +

    Computes values across the entire index

    + + @member ejs.TermStatsFacet + @param {Boolean} trueFalse Calculate facet counts globally or not. + @returns {Object} returns this so that calls can be chained. + */ + global: function (trueFalse) { + if (trueFalse == null) { + return facet[name].global; + } + + facet[name].global = trueFalse; + return this; + }, + + /** +

    Computes values across the the specified scope

    + + @member ejs.TermStatsFacet + @param {String} scope The scope name to calculate facet counts with. + @returns {Object} returns this so that calls can be chained. + */ + scope: function (scope) { + if (scope == null) { + return facet[name].scope; + } + + facet[name].scope = scope; + return this; + }, + + /** +

    Enables caching of the facetFilter

    + + @member ejs.TermStatsFacet + @param {Boolean} trueFalse If the facetFilter should be cached or not + @returns {Object} returns this so that calls can be chained. + */ + cacheFilter: function (trueFalse) { + if (trueFalse == null) { + return facet[name].cache_filter; + } + + facet[name].cache_filter = trueFalse; + return this; + }, + + /** +

    Sets the path to the nested document if faceting against a + nested field.

    + + @member ejs.TermStatsFacet + @param {String} path The nested path + @returns {Object} returns this so that calls can be chained. + */ + nested: function (path) { + if (path == null) { + return facet[name].nested; + } + + facet[name].nested = path; + return this; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal facet object. This is typically used by + internal API functions so use with caution.

    + + @member ejs.TermStatsFacet + @returns {String} returns this object's internal facet 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 _self() + + @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 this 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 this 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 this 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 this 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 +

    A BoolFilter allows you to build Boolean 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. Use _self() + @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 this 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 this 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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.BoolFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    An existsFilter matches documents where the specified field is present + and the field contains a legitimate value.

    + + @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 get() + + @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 this 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 this 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 +

    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.

    + + @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 _self() + + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 +

    A filter that restricts matched results/docs to a given distance from the + point of origin. The format conforms with the GeoJSON specification.

    + + @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 _self() + + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 +

    A filter that restricts matched results/docs to a given distance range from the + point of origin. The format conforms with the GeoJSON specification.

    + + @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 _self() + + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 +

    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.

    + + @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 _self() + + @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 this 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 GeoPoint object, it is added to the current + list of points. If passed an array of GeoPoint + objects it replaces all current values. + + @member ejs.GeoPolygonFilter + @param {Array} pointsArray the array of points that represent the polygon + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 +

    Efficient filtering of documents containing shapes indexed using the + geo_shape type.

    + +

    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.

    + + @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. Use _self() + @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 this 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 Shape object. + @returns {Object} returns this 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 IndexedShape object. + @returns {Object} returns this 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 IndexedShape object. + @returns {Object} returns this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.GeoShapeFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    The has_child filter results in parent documents that have child docs + matching the query being returned.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.HasChildFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    The has_parent results in child documents that have parent docs matching + the query being returned.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.HasParentFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.IdsFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.IndicesFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    A limit filter limits the number of documents (per shard) to execute on.

    + + @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. Use get() + @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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.LimitFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    This filter can be used to match on all the documents + in a given set of collections and/or types.

    + + @name ejs.MatchAllFilter + + @desc +

    A filter that matches on all documents

    + + */ + ejs.MatchAllFilter = function () { + + /** + The internal Query object. Use get(). + @member ejs.MatchAllFilter + @property {Object} filter + */ + var filter = { + match_all: {} + }; + + return { + + /** + Serializes the internal filter 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 filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    An missingFilter matches documents where the specified field contains no legitimate value.

    + + @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 get() + + @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 this 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 this 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 this 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 this 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 +

    Nested filters allow you to search against content within objects that are + embedded inside of other objects. It is similar to XPath + expressions in XML both conceptually and syntactically.

    + +

    + 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).

    + + @name ejs.NestedFilter + + @desc +

    Constructs a filter that is capable of executing a filter against objects + nested within a document.

    + + @param {String} path The nested object path. + + */ + ejs.NestedFilter = function (path) { + + /** + The internal Filter object. Use _self(). + @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 this 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 this 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 this 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 Query. + + @member ejs.NestedFilter + @param {Double} boost A positive double value. + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 filter 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 filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    A container Filter that excludes the documents matched by the + contained filter.

    + + @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 _self() + + @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 this 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 this 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 this 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 this 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 +

    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.

    + +

    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.

    + +

    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.

    + + @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 get() + + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 _self() + + @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 this 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 this 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 this 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 this 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 +

    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.

    + + @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 get() + + @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 this 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 this 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 this 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 this 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 this 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 +

    Wraps any query to be used as a filter. Can be placed within queries + that accept a filter.

    + +

    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.

    + +

    Note, the process of caching the first execution is higher when not + caching (since it needs to satisfy different queries).

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.QueryFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    Matches documents with fields that have terms within a certain range.

    + + @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. Use get() + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.RangeFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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: + 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 this 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 flags option. + + @member ejs.RegexpFilter + @param {String} v The flags as a numeric value. + @returns {Object} returns this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.RegexpFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    A filter allowing to define scripts as filters

    + + @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. Use get() + @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 this 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 this 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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.ScriptFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    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.

    + + @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 this 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 this 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 this 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 this 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 this 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 +

    Filters documents that have fields that match any of the provided + terms (not analyzed)

    + + @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. Use get() + @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 this 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 this 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 this 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 this 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 this 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 this 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 filter object. This is typically used by + internal API functions so use with caution. + + @member ejs.TermsFilter + @returns {String} returns this object's internal filter property. + */ + _self: function () { + return filter; + } + }; + }; + + /** + @class +

    A Filter that filters results by a specified index type.

    + + @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 get() + + @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 this 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 +

    The Document 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.

    + + @name ejs.Document + + @desc + Object used to create, replace, update, and delete documents + +
    +

    + Tip: + 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. +

    +
    + + @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¶m2=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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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; + }, + + /** +

    Allows you to serialize this object into a JSON encoded string.

    + + @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'; + }, + + /** +

    Retrieves the internal document object. This is + typically used by internal API functions so use with caution.

    + + @member ejs.Document + @returns {Object} returns this object's internal object. + */ + _self: function () { + return params; + }, + + /** +

    Retrieves a document from the given index and type.

    + + @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); + }, + + /** +

    Stores a document in the given index and type. If no id + is set, one is created during indexing.

    + + @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; + }, + + /** +

    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.

    + + @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); + }, + + /** +

    Deletes the document from the given index and type using the + speciifed id.

    + + @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 +

    A boolQuery allows you to build Boolean query constructs + from individual term or phrase queries. For example you might want to search + for documents containing the terms javascript and python.

    + + @name ejs.BoolQuery + + @desc + A Query that matches documents matching boolean combinations of other + queries, e.g. termQuerys, phraseQuerys or other boolQuerys. + + */ + ejs.BoolQuery = function () { + + /** + The internal query object. Use _self() + @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 Query object + @returns {Object} returns this 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 this 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 this 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 Query. + + @member ejs.BoolQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 Query. Default: false. + + @member ejs.BoolQuery + @param {String} trueFalse A true/falsethis 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 integer value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.BoolQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @name ejs.BoostingQuery + + @desc +

    Constructs a query that can demote search results. A negative boost.

    + + @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 _self(). + @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 Query object + @returns {Object} returns this 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 positive + query that will be negatively boosted. + + @member ejs.BoostingQuery + @param {Object} oQuery A valid Query object + @returns {Object} returns this 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 double value where 0 < n < 1. + @returns {Object} returns this 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 Query. + + @member ejs.BoostingQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    A constant score query wraps another Query or + Filter and returns a constant score for each + result that is equal to the query boost.

    + +

    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.

    + + @name ejs.ConstantScoreQuery + + @desc +

    Constructs a query where each documents returned by the internal + query or filter have a constant score equal to the boost factor.

    + + */ + ejs.ConstantScoreQuery = function () { + + /** + The internal Query object. Use _self(). + @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 Query object + @returns {Object} returns this 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 Filter object + @returns {Object} returns this 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 this 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 this 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 Query. + + @member ejs.ConstantScoreQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 Query. + + @member ejs.CustomBoostFactorQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.CustomBoostFactorQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + +

    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.

    + + @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. Use _self() + @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 this 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. + + + {filter: someFilter, boost: 2.1} + + + @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 this 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 this 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 this 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 this 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 double value. + @returns {Object} returns this 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 Query. + + @member ejs.CustomFiltersScoreQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.CustomFiltersScoreQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 Query. + + @member ejs.CustomScoreQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.CustomScoreQuery + @returns {String} returns this object's internal query 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 termQuerys, phraseQuerys, boolQuerys, etc. + + */ + ejs.DisMaxQuery = function () { + + /** + The internal query object. Use _self() + @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 this 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 Query. Default: 1.0. + + @member ejs.DisMaxQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.DisMaxQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 Query. + + @member ejs.FieldMaskingSpanQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.FieldMaskingSpanQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class + A query that executes against a given field or document property. It is a simplified version + of the queryString 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. Use get() + @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 this 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 this 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 Boolean operator. This operator is used + to join individual query terms when no operator is explicity used + in the query string (i.e., this AND that). + Defaults to OR (same as Google). + + @member ejs.FieldQuery + @param {String} op The operator, AND or OR. + @returns {Object} returns this 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 Query object. + + @member ejs.FieldQuery + @param {String} analyzer A valid analyzer name. + @returns {Object} returns this 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 query + when in quoted text. + + @member ejs.FieldQuery + @param {String} analyzer A valid analyzer name. + @returns {Object} returns this 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 true/false value. + @returns {Object} returns this 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 Query. Default: true. + + @member ejs.FieldQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 Query + since they are not analyzed. Default: true. + + @member ejs.FieldQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 + Query. Default: true. + + @member ejs.FieldQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 double value between 0 and 1. + @returns {Object} returns this 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 Query. Default: 1.0. + + @member ejs.FieldQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 this 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 this 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 this 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 integer value. + @returns {Object} returns this 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 + Query. By default, wildcard terms are not analyzed. + Analysis of wildcard characters is not perfect. Default: false. + + @member ejs.FieldQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 true/false value. + @returns {Object} returns this 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 Query should match. + + @member ejs.FieldQuery + @param {Integer} minMatch An integer between 0 and 100. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.FieldQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Filter queries allow you to restrict the results returned by a query. There are + several different types of filters that can be applied + (see filter module). A filterQuery + takes a Query and a Filter object as arguments and constructs + a new Query that is then used for the search.

    + + @name ejs.FilteredQuery + + @desc +

    A query that applies a filter to the results of another query.

    + + @param {Object} someQuery a valid Query object + @param {Object} someFilter a valid Filter 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 _self() + @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 Query object + @returns {Object} returns this 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 Filter object + @returns {Object} returns this 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 this 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 this 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 this 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 Query. + + @member ejs.FilteredQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 +

    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).

    + +

    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.

    + +

    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.

    + + @name ejs.FuzzyLikeThisFieldQuery + + @desc +

    Constructs a query where each documents returned are “like” provided text

    + + @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 get(). + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 Query. + + @member ejs.FuzzyLikeThisFieldQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Fuzzy like this query find documents that are “like” provided text by + running it against one or more fields.

    + +

    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.

    + +

    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.

    + + @name ejs.FuzzyLikeThisQuery + + @desc +

    Constructs a query where each documents returned are “like” provided text

    + + @param {String} likeText The text to find documents like it. + */ + ejs.FuzzyLikeThisQuery = function (likeText) { + + /** + The internal Query object. Use get(). + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 Query. + + @member ejs.FuzzyLikeThisQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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> + +

    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".

    + + @name ejs.FuzzyQuery + + @desc +

    Constructs a query where each documents returned are “like” provided text

    + + @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 get(). + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 Query. + + @member ejs.FuzzyQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Efficient querying of documents containing shapes indexed using the + geo_shape type.

    + +

    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.

    + + @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. Use _self() + @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 this 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 Shape object. + @returns {Object} returns this 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 IndexedShape object. + @returns {Object} returns this 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 IndexedShape object. + @returns {Object} returns this 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 Query. + + @member ejs.GeoShapeQuery + @param {Number} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.GeoShapeQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 Query. + + @member ejs.HasChildQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.HasChildQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 Query. + + @member ejs.HasParentQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.HasParentQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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 Query. + + @member ejs.IdsQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.IdsQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 this 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 this 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 Query. + + @member ejs.IndicesQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.IndicesQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    This query can be used to match all the documents + in a given set of collections and/or types.

    + + @name ejs.MatchAllQuery + + @desc +

    A query that returns all documents.

    + + */ + ejs.MatchAllQuery = function () { + + /** + The internal Query object. Use get(). + @member ejs.MatchAllQuery + @property {Object} query + */ + var query = { + match_all: {} + }; + + return { + + /** + Sets the boost value of the Query. + + @member ejs.MatchAllQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class + A MatchQuery is a type of Query that accepts + text/numerics/dates, analyzes it, generates a query based on the + MatchQuery 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. Use get() + @member ejs.MatchQuery + @property {Object} query + */ + var query = { + match: {} + }; + + query.match[field] = { + query: qstr + }; + + return { + + /** + Sets the boost value for documents matching the Query. + + @member ejs.MatchQuery + @param {Number} boost A positive double value. + @returns {Object} returns this 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 Query. + + @member ejs.MatchQuery + @param {String} qstr The query string to search for. + @returns {Object} returns this 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 MatchQuery. Valid values are + boolean, phrase, and phrase_prefix. + + @member ejs.MatchQuery + @param {String} type Any of boolean, phrase, phrase_prefix. + @returns {Object} returns this 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 Query. + + @member ejs.MatchQuery + @param {Double} fuzz A double value between 0.0 and 1.0. + @returns {Object} returns this 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 MatchQuery. + + @member ejs.MatchQuery + @param {Integer} l A positive integer length value. + @returns {Object} returns this 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 MatchQuery. + + @member ejs.MatchQuery + @param {Integer} e A positive integer value. + @returns {Object} returns this 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 Query. Default: or. + + @member ejs.MatchQuery + @param {String} op Any of "and" or "or", no quote characters. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 Query object. + + @member ejs.MatchQuery + @param {String} analyzer A valid analyzer name. + @returns {Object} returns this 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 Query should match. + + @member ejs.MatchQuery + @param {Integer} minMatch An integer between 0 and 100. + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.MatchQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    The more_like_this_field query is the same as the more_like_this query, + except it runs against a single field.

    + + @name ejs.MoreLikeThisFieldQuery + + @desc +

    Constructs a query where each documents returned are “like” provided text

    + + @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 get(). + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 double value. + @returns {Object} returns this 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 Query. + + @member ejs.MoreLikeThisFieldQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    More like this query find documents that are “like” provided text by + running it against one or more fields.

    + + @name ejs.MoreLikeThisQuery + + @desc +

    Constructs a query where each documents returned are “like” provided text

    + + @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 get(). + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 double value. + @returns {Object} returns this 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 Query. + + @member ejs.MoreLikeThisQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class + A MultiMatchQuery query builds further on top of the + MatchQuery 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. Use get() + @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 this 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 + + DisjunctionMaxQuery + + @member ejs.MultiMatchQuery + @param {String} trueFalse A true/false value. + @returns {Object} returns this 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 double value. + @returns {Object} returns this 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 Query should match. + + @member ejs.MultiMatchQuery + @param {Integer} minMatch An integer between 0 and 100. + @returns {Object} returns this 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 this 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 this 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 this 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 Query. + + @member ejs.MultiMatchQuery + @param {Number} boost A positive double value. + @returns {Object} returns this 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 Query. + + @member ejs.MultiMatchQuery + @param {String} qstr The query string to search for. + @returns {Object} returns this 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 MultiMatchQuery. 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 this 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 Query. + + @member ejs.MultiMatchQuery + @param {Double} fuzz A double value between 0.0 and 1.0. + @returns {Object} returns this 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 Query. + + @member ejs.MultiMatchQuery + @param {Integer} l A positive integer length value. + @returns {Object} returns this 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 Query. + + @member ejs.MultiMatchQuery + @param {Integer} e A positive integer value. + @returns {Object} returns this 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 Query. Default: or. + + @member ejs.MultiMatchQuery + @param {String} op Any of "and" or "or", no quote characters. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 Query object. + + @member ejs.MultiMatchQuery + @param {String} analyzer A valid analyzer name. + @returns {Object} returns this 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 Query object. This is typically used by + internal API functions so use with caution. + + @member ejs.MultiMatchQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Nested queries allow you to search against content within objects that are + embedded inside of other objects. It is similar to XPath expressions + in XML both conceptually and syntactically.

    + +

    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).

    + + @name ejs.NestedQuery + + @desc +

    Constructs a query that is capable of executing a search against objects + nested within a document.

    + + @param {String} path The nested object path. + + */ + ejs.NestedQuery = function (path) { + + /** + The internal Query object. Use _self(). + + @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 this 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 this 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 this 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 this 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 this 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 Query. + + @member ejs.NestedQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query 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 query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Matches documents that have fields containing terms with a specified + prefix (not analyzed). The prefix query maps to Lucene PrefixQuery.

    + + @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. Use get() + @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 this 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 this 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 this 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 Query. + + @member ejs.PrefixQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.PrefixQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + +

    See the Lucene Query Parser Syntax + for more information.

    + + @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 get(). + @member ejs.QueryStringQuery + @property {Object} query + */ + var query = { + query_string: {} + }; + + query.query_string.query = qstr; + + return { + + /** + Sets the query string on this Query object. + + @member ejs.QueryStringQuery + @param {String} qstr A valid Lucene query string. + @returns {Object} returns this 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 this 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 this 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 + + DisjunctionMaxQuery + + @member ejs.QueryStringQuery + @param {String} trueFalse A true/false value. + @returns {Object} returns this 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 Boolean operator. This operator is used to join individual query + terms when no operator is explicity used in the query string (i.e., this AND that). + Defaults to OR (same as Google). + + @member ejs.QueryStringQuery + @param {String} op The operator to use, AND or OR. + @returns {Object} returns this 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 Query object. + + @member ejs.QueryStringQuery + @param {String} analyzer A valid analyzer name. + @returns {Object} returns this 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 query + when in quoted text. + + @member ejs.QueryStringQuery + @param {String} analyzer A valid analyzer name. + @returns {Object} returns this 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 Query. Default: true. + + @member ejs.QueryStringQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 Query + since they are not analyzed. Default: true. + + @member ejs.QueryStringQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 + Query. Default: true. + + @member ejs.QueryStringQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 double value between 0 and 1. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 Query. Default: 1.0. + + @member ejs.QueryStringQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 + Query. By default, wildcard terms are not analyzed. + Analysis of wildcard characters is not perfect. Default: false. + + @member ejs.QueryStringQuery + @param {Boolean} trueFalse A true/false value. + @returns {Object} returns this 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 true/false value. + @returns {Object} returns this 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 Query should match. + + @member ejs.QueryStringQuery + @param {Integer} minMatch An integer between 0 and 100. + @returns {Object} returns this 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 Query using + DisMax. 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 double value. + @returns {Object} returns this 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 true/false value. + @returns {Object} returns this 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 integer value. + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.QueryStringQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 Query. + + @member ejs.RangeQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.RangeQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Matches documents that have fields matching a regular expression. Based + on Lucene 4.0 RegexpQuery which uses automaton to efficiently iterate over + index terms.

    + + @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. Use get() + @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 this 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 this 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: + 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 this 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 flags option. + + @member ejs.RegexpQuery + @param {String} v The flags as a numeric value. + @returns {Object} returns this 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 this 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 Query. + + @member ejs.RegexpQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.RegexpQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Matches spans near the beginning of a field. The spanFirstQuery allows you to search + for Spans that start and end within the first n positions of the document. + The span first query maps to Lucene SpanFirstQuery.

    + + @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. Use _self() + @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 this 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 this 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 Query. + + @member ejs.SpanFirstQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.SpanFirstQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    A spanNearQuery will look to find a number of spanQuerys within a given + distance from each other.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 Query. + + @member ejs.SpanNearQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.SpanNearQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    Removes matches which overlap with another span query. + The span not query maps to Lucene SpanNotQuery.

    + + @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. Use _self() + @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 this 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 this 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 Query. + + @member ejs.SpanNotQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.SpanNotQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use _self() + @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 this 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 Query. + + @member ejs.SpanOrQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.SpanOrQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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 Query. + + @member ejs.SpanTermQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.SpanTermQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    A TermQuery can be used to return documents containing a given + keyword or term. For instance, you might want to retieve all the + documents/objects that contain the term Javascript. Term filters + often serve as the basis for more complex queries such as Boolean queries.

    + + @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. Use get() + @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 this 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 this 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 Query. + + @member ejs.TermQuery + @param {Number} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.TermQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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 this 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 Query. Default: false. + + @member ejs.TermsQuery + @param {String} trueFalse A true/falsethis 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 Query. + + @member ejs.TermsQuery + @param {Number} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.TermsQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + +

    The top_children also provide scoring capabilities, with the ability to + specify max, sum or avg as the score type.

    + + @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. Use _self() + @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 this 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 this 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 this 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 this 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 this 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 this 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 Query. + + @member ejs.TopChildrenQuery + @param {Double} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.TopChildrenQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    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.

    + + @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. Use get() + @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 this 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 this 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 this 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 Query. + + @member ejs.WildcardQuery + @param {Number} boost A positive double value. + @returns {Object} returns this 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 query object. This is typically used by + internal API functions so use with caution. + + @member ejs.WildcardQuery + @returns {String} returns this object's internal query property. + */ + _self: function () { + return query; + } + }; + }; + + /** + @class +

    A GeoPoint object that can be used in queries and filters that + take a GeoPoint. GeoPoint supports various input formats.

    + +

    See http://www.elasticsearch.org/guide/reference/mapping/geo-point-type.html

    + + @name ejs.GeoPoint + + @desc +

    Defines a point

    + + @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 this 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 this 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 this 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 this 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 script 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 +

    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.

    + +

    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.

    + +

    See http://www.elasticsearch.org/guide/reference/api/search/highlighting.html

    + + @name ejs.Highlight + + @desc +

    Allows to highlight search results on one or more fields.

    + + @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 this 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 + oField 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 this 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 + oField 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 this 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 + oField 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 this 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 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 this 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 oField 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 this 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 oField parameter. Default: 100 + + @member ejs.Highlight + @param {Integer} size The fragment size in characters. + @param {String} oField An optional field name + @returns {Object} returns this 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 oField parameter. Default: 5 + + @member ejs.Highlight + @param {Integer} cnt The fragment size in characters. + @param {String} oField An optional field name + @returns {Object} returns this 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 this 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 oField + 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 this 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 + oField parameter. Default: 20 + + @member ejs.Highlight + @param {Integer} cnt The max characters to scan. + @param {String} oField An optional field name + @returns {Object} returns this 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 oField 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 this 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 + oField 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 this 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 + oField 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 this 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 script 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 +

    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.

    + + @name ejs.IndexedShape + + @desc +

    Defines a shape that already exists in an index/type.

    + + @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 this 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 this 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 this 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 this 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 script 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 +

    The Request object provides methods generating and + executing search requests.

    + + @name ejs.Request + + @desc +

    Provides methods for executing search requests

    + + @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 Sort objects when possible. + + @member ejs.Request + @param {String} fieldName The field to be sorted by. + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 this 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 + from parameter allows you to page through the result set + by making multiple request. This parameters specifies the starting + result/document number point. Combine with size() 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 this 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 Query object. + @returns {Object} returns this 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 this 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 this 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 Facet object. + @returns {Object} returns this 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 Filter object. + @returns {Object} returns this 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 Highlight + settings. + + @member ejs.Request + @param {Highlight} h A valid Highlight object + @returns {Object} returns this 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 ScriptField. + + @member ejs.Request + @param {ScriptField} oScriptField A valid ScriptField. + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 double value. + @returns {Object} returns this 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 query 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 +

    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 + price and quantity. At query time, you could define a computed + property that dynamically creates a new field called totalin each document + based on the calculation price * quantity.

    + + @name ejs.ScriptField + + @desc +

    Computes dynamic document properties based on information from other fields.

    + + @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 + javascript and mvel. + + @member ejs.ScriptField + @param {String} language The language of the script. + @returns {Object} returns this 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 this 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 this 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 script object. This is typically used by + internal API functions so use with caution. + + @member ejs.ScriptField + @returns {String} returns this object's internal facet property. + */ + _self: function () { + return script; + } + }; + }; + + /** + @class +

    A Shape object that can be used in queries and filters that + take a Shape. Shape uses the GeoJSON format.

    + +

    See http://www.geojson.org/

    + + @name ejs.Shape + + @desc +

    Defines a shape

    + + @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 this 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 this 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 script 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 +

    A Sort object that can be used in on the Request object to specify + various types of sorting.

    + +

    See http://www.elasticsearch.org/guide/reference/api/search/sort.html

    + + @name ejs.Sort + + @desc +

    Defines a sort value

    + + @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 this 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 this 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 this 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 this 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 + order('asc'). + + @member ejs.Sort + @returns {Object} returns this so that calls can be chained. + */ + asc: function () { + sort[key].order = 'asc'; + return this; + }, + + /** + Sets the sort order to descending (desc). Same as calling + order('desc'). + + @member ejs.Sort + @returns {Object} returns this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 this 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 script 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 --git a/common/lib/elastic.min.js b/common/lib/elastic.min.js new file mode 100644 index 00000000000..b63fb6de718 --- /dev/null +++ b/common/lib/elastic.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 */ +(function(){"use strict";var e=this,t=e&&e.ejs,n=Array.prototype,r=Object.prototype,i=n.slice,s=r.toString,o=r.hasOwnProperty,u=n.forEach,a=Array.isArray,f={},l,c,h,p,d,v,m,g,y,b,w,E,S,x,T,N,C,k,L;typeof exports!="undefined"?L=exports:L=e.ejs={},l=function(e,t){return o.call(e,t)},c=function(e,t,n){if(e==null)return;if(u&&e.forEach===u)e.forEach(t,n);else if(e.length===+e.length){for(var r=0,i=e.length;r0&&(n=["_all"]),e.routing!=null?i=e.routing:i="",{sort:function(){var e,n;l(t,"sort")||(t.sort=[]);if(arguments.length===0)return t.sort;if(arguments.length===1){var r=arguments[0];if(v(r))t.sort.push(r);else if(C(r))t.sort.push(r._self());else{if(!p(r))throw new TypeError("Argument must be string, Sort, or array");t.sort=[];for(e=0,n=r.length;e0&&(n=["_all"]),this},types:function(e){if(e==null)return r;if(v(e))r=[e];else{if(!p(e))throw new TypeError("Argument must be a string or array");r=e}return n.length===0&&r.length>0&&(n=["_all"]),this},facet:function(e){if(e==null)return t.facets;t.facets==null&&(t.facets={});if(!E(e))throw new TypeError("Argument must be a Facet");return h(t.facets,e._self()),this},filter:function(e){if(e==null)return t.filter;if(!w(e))throw new TypeError("Argument must be a Filter");return t.filter=e._self(),this},highlight:function(e){if(e==null)return t.highlight;if(!k(e))throw new TypeError("Argument must be a Highlight object");return t.highlight=e._self(),this},scriptField:function(e){if(e==null)return t.script_fields;t.script_fields==null&&(t.script_fields={});if(!S(e))throw new TypeError("Argument must be a ScriptField");return h(t.script_fields,e._self()),this},preference:function(e){return e==null?t.preference:(t.preference=e,this)},indexBoost:function(e,n){return t.indices_boost==null&&(t.indices_boost={}),arguments.length===0?t.indices_boost:(t.indices_boost[e]=n,this)},explain:function(e){return e==null?t.explain:(t.explain=e,this)},version:function(e){return e==null?t.version:(t.version=e,this)},minScore:function(e){return e==null?t.min_score:(t.min_score=e,this)},toString:function(){return JSON.stringify(t)},_type:function(){return"request"},_self:function(){return t},doSearch:function(e){var s=JSON.stringify(t),o="";if(L.client==null)throw new Error("No Client Set");return n.length>0&&(o=o+"/"+n.join()),r.length>0&&(o=o+"/"+r.join()),o+="/_search",i!==""&&(o=o+"?routing="+encodeURIComponent(i)),L.client.post(o,s,e)}}},L.ScriptField=function(e){var t={};return t[e]={},{lang:function(n){return n==null?t[e].lang:(t[e].lang=n,this)},script:function(n){return n==null?t[e].script:(t[e].script=n,this)},params:function(n){return n==null?t[e].params:(t[e].params=n,this)},toString:function(){return JSON.stringify(t)},_type:function(){return"script field"},_self:function(){return t}}},L.Shape=function(e,t){var n={},r=function(e){var t=!1;if(e==="point"||e==="linestring"||e==="polygon"||e==="multipoint"||e==="envelope"||e==="multipolygon")t=!0;return t};return e=e.toLowerCase(),r(e)&&(n.type=e,n.coordinates=t),{type:function(e){return e==null?n.type:(e=e.toLowerCase(),r(e)&&(n.type=e),this)},coordinates:function(e){return e==null?n.coordinates:(n.coordinates=e,this)},toString:function(){return JSON.stringify(n)},_type:function(){return"shape"},_self:function(){return n}}},L.Sort=function(e){e==null&&(e="_score");var t={},n=e,r="_geo_distance",i="_script";return t[n]={},{field:function(r){var i=t[n];return r==null?e:(delete t[n],e=r,n=r,t[n]=i,this)},geoDistance:function(i){var s=t[n];if(i==null)return t[n][e];if(!x(i))throw new TypeError("Argument must be a GeoPoint");return delete t[n],n=r,t[n]=s,t[n][e]=i._self(),this},script:function(e){var r=t[n];return e==null?t[n].script:(delete t[n],n=i,t[n]=r,t[n].script=e,this)},order:function(e){if(e==null)return t[n].order;e=e.toLowerCase();if(e==="asc"||e==="desc")t[n].order=e;return this},asc:function(){return t[n].order="asc",this},desc:function(){return t[n].order="desc",this},reverse:function(e){return e==null?t[n].reverse:(t[n].reverse=e,this)},missing:function(e){return e==null?t[n].missing:(t[n].missing=e,this)},ignoreUnmapped:function(e){return e==null?t[n].ignore_unmapped:(t[n].ignore_unmapped=e,this)},unit:function(e){if(e==null)return t[n].unit;e=e.toLowerCase();if(e==="mi"||e==="km")t[n].unit=e;return this},normalize:function(e){return e==null?t[n].normalize:(t[n].normalize=e,this)},distanceType:function(e){if(e==null)return t[n].distance_type;e=e.toLowerCase();if(e==="arc"||e==="plane")t[n].distance_type=e;return this},params:function(e){return e==null?t[n].params:(t[n].params=e,this)},lang:function(e){return e==null?t[n].lang:(t[n].lang=e,this)},type:function(e){if(e==null)return t[n].type;e=e.toLowerCase();if(e==="string"||e==="number")t[n].type=e;return this},toString:function(){return JSON.stringify(t)},_type:function(){return"sort"},_self:function(){return t}}},L.noConflict=function(){return e.ejs=t,this}}).call(this); \ No newline at end of file diff --git a/common/lib/jquery-1.8.0.min.js b/common/lib/jquery-1.8.0.min.js new file mode 100644 index 00000000000..8156913a725 --- /dev/null +++ b/common/lib/jquery-1.8.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v@1.8.0 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bR[a]=c,c}function ch(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||cd.test(a)?d(a,e):ch(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ch(a+"["+e+"]",b[e],c,d);else d(a,b)}function cy(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.0",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return typeof a=="object"?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
    a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length||!d)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
    t
    ",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
    ",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/^(?:\{.*\}|\[.*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||++p.uuid:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.shift(),e=p._queueHooks(a,b),f=function(){p.dequeue(a,b)};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),delete e.stop,d.call(a,f,e)),!c.length&&e&&e.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c-1)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c-1)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,""+d),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;jq&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bd(a,b,c,d){var e=0,f=b.length;for(;e0?h(g,c,f):[]}function bf(a,c,d,e,f){var g,h,i,j,k,l,m,n,p=0,q=f.length,s=L.POS,t=new RegExp("^"+s.source+"(?!"+r+")","i"),u=function(){var a=1,c=arguments.length-2;for(;ai){m=a.slice(i,g.index),i=n,l=[c],B.test(m)&&(k&&(l=k),k=e);if(h=H.test(m))m=m.slice(0,-5).replace(B,"$&*");g.length>1&&g[0].replace(t,u),k=be(m,g[1],g[2],l,k,h)}}k?(j=j.concat(k),(m=a.slice(i))&&m!==")"?B.test(m)?bd(m,j,d,e):Z(m,c,d,e?e.concat(k):k):o.apply(d,j)):Z(a,c,d,e)}return q===1?d:Z.uniqueSort(d)}function bg(a,b,c){var d,e,f,g=[],i=0,j=D.exec(a),k=!j.pop()&&!j.pop(),l=k&&a.match(C)||[""],m=$.preFilter,n=$.filter,o=!c&&b!==h;for(;(e=l[i])!=null&&k;i++){g.push(d=[]),o&&(e=" "+e);while(e){k=!1;if(j=B.exec(e))e=e.slice(j[0].length),k=d.push({part:j.pop().replace(A," "),captures:j});for(f in n)(j=L[f].exec(e))&&(!m[f]||(j=m[f](j,b,c)))&&(e=e.slice(j.shift().length),k=d.push({part:f,captures:j}));if(!k)break}}return k||Z.error(a),g}function bh(a,b,e){var f=b.dir,g=m++;return a||(a=function(a){return a===e}),b.first?function(b,c){while(b=b[f])if(b.nodeType===1)return a(b,c)&&b}:function(b,e){var h,i=g+"."+d,j=i+"."+c;while(b=b[f])if(b.nodeType===1){if((h=b[q])===j)return b.sizset;if(typeof h=="string"&&h.indexOf(i)===0){if(b.sizset)return b}else{b[q]=j;if(a(b,e))return b.sizset=!0,b;b.sizset=!1}}}}function bi(a,b){return a?function(c,d){var e=b(c,d);return e&&a(e===!0?c:e,d)}:b}function bj(a,b,c){var d,e,f=0;for(;d=a[f];f++)$.relative[d.part]?e=bh(e,$.relative[d.part],b):(d.captures.push(b,c),e=bi(e,$.filter[d.part].apply(null,d.captures)));return e}function bk(a){return function(b,c){var d,e=0;for(;d=a[e];e++)if(d(b,c))return!0;return!1}}var c,d,e,f,g,h=a.document,i=h.documentElement,j="undefined",k=!1,l=!0,m=0,n=[].slice,o=[].push,q=("sizcache"+Math.random()).replace(".",""),r="[\\x20\\t\\r\\n\\f]",s="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",t=s.replace("w","w#"),u="([*^$|!~]?=)",v="\\["+r+"*("+s+")"+r+"*(?:"+u+r+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+t+")|)|)"+r+"*\\]",w=":("+s+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",x=":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",y=r+"*([\\x20\\t\\r\\n\\f>+~])"+r+"*",z="(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|"+v+"|"+w.replace(2,7)+"|[^\\\\(),])+",A=new RegExp("^"+r+"+|((?:^|[^\\\\])(?:\\\\.)*)"+r+"+$","g"),B=new RegExp("^"+y),C=new RegExp(z+"?(?="+r+"*,|$)","g"),D=new RegExp("^(?:(?!,)(?:(?:^|,)"+r+"*"+z+")*?|"+r+"*(.*?))(\\)|$)"),E=new RegExp(z.slice(19,-6)+"\\x20\\t\\r\\n\\f>+~])+|"+y,"g"),F=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,G=/[\x20\t\r\n\f]*[+~]/,H=/:not\($/,I=/h\d/i,J=/input|select|textarea|button/i,K=/\\(?!\\)/g,L={ID:new RegExp("^#("+s+")"),CLASS:new RegExp("^\\.("+s+")"),NAME:new RegExp("^\\[name=['\"]?("+s+")['\"]?\\]"),TAG:new RegExp("^("+s.replace("[-","[-\\*")+")"),ATTR:new RegExp("^"+v),PSEUDO:new RegExp("^"+w),CHILD:new RegExp("^:(only|nth|last|first)-child(?:\\("+r+"*(even|odd|(([+-]|)(\\d*)n|)"+r+"*(?:([+-]|)"+r+"*(\\d+)|))"+r+"*\\)|)","i"),POS:new RegExp(x,"ig"),needsContext:new RegExp("^"+r+"*[>+~]|"+x,"i")},M={},N=[],O={},P=[],Q=function(a){return a.sizzleFilter=!0,a},R=function(a){return function(b){return b.nodeName.toLowerCase()==="input"&&b.type===a}},S=function(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}},T=function(a){var b=!1,c=h.createElement("div");try{b=a(c)}catch(d){}return c=null,b},U=T(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),V=T(function(a){a.id=q+0,a.innerHTML="
    ",i.insertBefore(a,i.firstChild);var b=h.getElementsByName&&h.getElementsByName(q).length===2+h.getElementsByName(q+0).length;return g=!h.getElementById(q),i.removeChild(a),b}),W=T(function(a){return a.appendChild(h.createComment("")),a.getElementsByTagName("*").length===0}),X=T(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==j&&a.firstChild.getAttribute("href")==="#"}),Y=T(function(a){return a.innerHTML="",!a.getElementsByClassName||a.getElementsByClassName("e").length===0?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length!==1)}),Z=function(a,b,c,d){c=c||[],b=b||h;var e,f,g,i,j=b.nodeType;if(j!==1&&j!==9)return[];if(!a||typeof a!="string")return c;g=ba(b);if(!g&&!d)if(e=F.exec(a))if(i=e[1]){if(j===9){f=b.getElementById(i);if(!f||!f.parentNode)return c;if(f.id===i)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(i))&&bb(b,f)&&f.id===i)return c.push(f),c}else{if(e[2])return o.apply(c,n.call(b.getElementsByTagName(a),0)),c;if((i=e[3])&&Y&&b.getElementsByClassName)return o.apply(c,n.call(b.getElementsByClassName(i),0)),c}return bm(a,b,c,d,g)},$=Z.selectors={cacheLength:50,match:L,order:["ID","TAG"],attrHandle:{},createPseudo:Q,find:{ID:g?function(a,b,c){if(typeof b.getElementById!==j&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==j&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==j&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:W?function(a,b){if(typeof b.getElementsByTagName!==j)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(K,""),a[3]=(a[4]||a[5]||"").replace(K,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||Z.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&Z.error(a[0]),a},PSEUDO:function(a){var b,c=a[4];return L.CHILD.test(a[0])?null:(c&&(b=D.exec(c))&&b.pop()&&(a[0]=a[0].slice(0,b[0].length-c.length-1),c=b[0].slice(0,-1)),a.splice(2,3,c||a[3]),a)}},filter:{ID:g?function(a){return a=a.replace(K,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(K,""),function(b){var c=typeof b.getAttributeNode!==j&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(K,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=M[a];return b||(b=M[a]=new RegExp("(^|"+r+")"+a+"("+r+"|$)"),N.push(a),N.length>$.cacheLength&&delete M[N.shift()]),function(a){return b.test(a.className||typeof a.getAttribute!==j&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return b?function(d){var e=Z.attr(d,a),f=e+"";if(e==null)return b==="!=";switch(b){case"=":return f===c;case"!=":return f!==c;case"^=":return c&&f.indexOf(c)===0;case"*=":return c&&f.indexOf(c)>-1;case"$=":return c&&f.substr(f.length-c.length)===c;case"~=":return(" "+f+" ").indexOf(c)>-1;case"|=":return f===c||f.substr(0,c.length+1)===c+"-"}}:function(b){return Z.attr(b,a)!=null}},CHILD:function(a,b,c,d){if(a==="nth"){var e=m++;return function(a){var b,f,g=0,h=a;if(c===1&&d===0)return!0;b=a.parentNode;if(b&&(b[q]!==e||!a.sizset)){for(h=b.firstChild;h;h=h.nextSibling)if(h.nodeType===1){h.sizset=++g;if(h===a)break}b[q]=e}return f=a.sizset-d,c===0?f===0:f%c===0&&f/c>=0}}return function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b,c,d){var e=$.pseudos[a]||$.pseudos[a.toLowerCase()];return e||Z.error("unsupported pseudo: "+a),e.sizzleFilter?e(b,c,d):e}},pseudos:{not:Q(function(a,b,c){var d=bl(a.replace(A,"$1"),b,c);return function(a){return!d(a)}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!$.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},contains:Q(function(a){return function(b){return(b.textContent||b.innerText||bc(b)).indexOf(a)>-1}}),has:Q(function(a){return function(b){return Z(a,b).length>0}}),header:function(a){return I.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:R("radio"),checkbox:R("checkbox"),file:R("file"),password:R("password"),image:R("image"),submit:S("submit"),reset:S("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return J.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b,c){return c?a.slice(1):[a[0]]},last:function(a,b,c){var d=a.pop();return c?a:[d]},even:function(a,b,c){var d=[],e=c?1:0,f=a.length;for(;e$.cacheLength&&delete O[P.shift()],g};Z.matches=function(a,b){return Z(a,null,null,b)},Z.matchesSelector=function(a,b){return Z(b,null,null,[a]).length>0};var bm=function(a,b,e,f,g){a=a.replace(A,"$1");var h,i,j,k,l,m,p,q,r,s=a.match(C),t=a.match(E),u=b.nodeType;if(L.POS.test(a))return bf(a,b,e,f,s);if(f)h=n.call(f,0);else if(s&&s.length===1){if(t.length>1&&u===9&&!g&&(s=L.ID.exec(t[0]))){b=$.find.ID(s[1],b,g)[0];if(!b)return e;a=a.slice(t.shift().length)}q=(s=G.exec(t[0]))&&!s.index&&b.parentNode||b,r=t.pop(),m=r.split(":not")[0];for(j=0,k=$.order.length;j",a.querySelectorAll("[selected]").length||e.push("\\["+r+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),T(function(a){a.innerHTML="

    ",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+r+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=e.length&&new RegExp(e.join("|")),bm=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a)))if(d.nodeType===9)try{return o.apply(f,n.call(d.querySelectorAll(a),0)),f}catch(i){}else if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){var j=d.getAttribute("id"),k=j||q,l=G.test(a)&&d.parentNode||d;j?k=k.replace(c,"\\$&"):d.setAttribute("id",k);try{return o.apply(f,n.call(l.querySelectorAll(a.replace(C,"[id='"+k+"'] $&")),0)),f}catch(i){}finally{j||d.removeAttribute("id")}}return b(a,d,f,g,h)},g&&(T(function(b){a=g.call(b,"div");try{g.call(b,"[test!='']:sizzle"),f.push($.match.PSEUDO)}catch(c){}}),f=new RegExp(f.join("|")),Z.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!ba(b)&&!f.test(c)&&(!e||!e.test(c)))try{var h=g.call(b,c);if(h||a||b.document&&b.document.nodeType!==11)return h}catch(i){}return Z(c,null,null,[b]).length>0})}(),Z.attr=p.attr,p.find=Z,p.expr=Z.selectors,p.expr[":"]=p.expr.pseudos,p.unique=Z.uniqueSort,p.text=Z.getText,p.isXMLDoc=Z.isXML,p.contains=Z.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
    ","
    "]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=0,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(g=b===e&&bA;(h=a[s])!=null;s++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{g=g||bk(b),l=l||g.appendChild(b.createElement("div")),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(f=n.length-1;f>=0;--f)p.nodeName(n[f],"tbody")&&!n[f].childNodes.length&&n[f].parentNode.removeChild(n[f])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l=g.lastChild}h.nodeType?t.push(h):t=p.merge(t,h)}l&&(g.removeChild(l),h=l=g=null);if(!p.support.appendChecked)for(s=0;(h=t[s])!=null;s++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(s=0;(h=t[s])!=null;s++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[s+1,0].concat(r)),s+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^margin/,bO=new RegExp("^("+q+")(.*)$","i"),bP=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bQ=new RegExp("^([-+])=("+q+")","i"),bR={},bS={position:"absolute",visibility:"hidden",display:"block"},bT={letterSpacing:0,fontWeight:400,lineHeight:1},bU=["Top","Right","Bottom","Left"],bV=["Webkit","O","Moz","ms"],bW=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return bZ(this,!0)},hide:function(){return bZ(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bW.apply(this,arguments):this.each(function(){(c?a:bY(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bX(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bQ.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bX(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bT&&(f=bT[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(a,b){var c,d,e,f,g=getComputedStyle(a,null),h=a.style;return g&&(c=g[b],c===""&&!p.contains(a.ownerDocument.documentElement,a)&&(c=p.style(a,b)),bP.test(c)&&bN.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=c,c=g.width,h.width=d,h.minWidth=e,h.maxWidth=f)),c}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bP.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0||bH(a,"display")!=="none"?ca(a,b,d):p.swap(a,bS,function(){return ca(a,b,d)})},set:function(a,c,d){return b$(a,c,d?b_(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bP.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bU[d]+b]=e[d]||e[d-2]||e[0];return f}},bN.test(a)||(p.cssHooks[a+b].set=b$)});var cc=/%20/g,cd=/\[\]$/,ce=/\r?\n/g,cf=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,cg=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||cg.test(this.nodeName)||cf.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(ce,"\r\n")}}):{name:b.name,value:c.replace(ce,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ch(d,a[d],c,f);return e.join("&").replace(cc,"+")};var ci,cj,ck=/#.*$/,cl=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cm=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,cn=/^(?:GET|HEAD)$/,co=/^\/\//,cp=/\?/,cq=/)<[^<]*)*<\/script>/gi,cr=/([?&])_=[^&]*/,cs=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,ct=p.fn.load,cu={},cv={},cw=["*/"]+["*"];try{ci=f.href}catch(cx){ci=e.createElement("a"),ci.href="",ci=ci.href}cj=cs.exec(ci.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&ct)return ct.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
    ").append(a.replace(cq,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cA(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cA(a,b),a},ajaxSettings:{url:ci,isLocal:cm.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cw},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cy(cu),ajaxTransport:cy(cv),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cB(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cC(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=""+(c||y),k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cl.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(ck,"").replace(co,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=cs.exec(l.url.toLowerCase()),l.crossDomain=!(!i||i[1]==cj[1]&&i[2]==cj[2]&&(i[3]||(i[1]==="http:"?80:443))==(cj[3]||(cj[1]==="http:"?80:443)))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cz(cu,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!cn.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cp.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cr,"$1_="+z);l.url=A+(A===l.url?(cp.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cw+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cz(cv,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cD=[],cE=/\?/,cF=/(=)\?(?=&|$)|\?\?/,cG=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cD.pop()||p.expando+"_"+cG++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cF.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cF.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cF,"$1"+f):m?c.data=i.replace(cF,"$1"+f):k&&(c.url+=(cE.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cD.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cH,cI=a.ActiveXObject?function(){for(var a in cH)cH[a](0,1)}:!1,cJ=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cK()||cL()}:cK,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cI&&delete cH[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cJ,cI&&(cH||(cH={},p(a).unload(cI)),cH[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cM,cN,cO=/^(?:toggle|show|hide)$/,cP=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cQ=/queueHooks$/,cR=[cX],cS={"*":[function(a,b){var c,d,e,f=this.createTween(a,b),g=cP.exec(b),h=f.cur(),i=+h||0,j=1;if(g){c=+g[2],d=g[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&i){i=p.css(f.elem,a,!0)||c||1;do e=j=j||".5",i=i/j,p.style(f.elem,a,i+d),j=f.cur()/h;while(j!==1&&j!==e)}f.unit=d,f.start=i,f.end=g[1]?i+(g[1]+1)*c:c}return f}]};p.Animation=p.extend(cV,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c$.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c$.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=c_(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); diff --git a/common/lib/jquery.flot.js b/common/lib/jquery.flot.js new file mode 100644 index 00000000000..918352a5c65 --- /dev/null +++ b/common/lib/jquery.flot.js @@ -0,0 +1,2691 @@ +/* Javascript plotting library for jQuery, version 0.8 alpha. + +Copyright (c) 2007-2012 IOLA and Ole Laursen. +Licensed under the MIT license. + +*/ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ +(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + +// the actual Flot code +(function($) { + function Plot(placeholder, data_, options_, plugins) { + // data is on the form: + // [ series1, series2 ... ] + // where series is either just the data as [ [x1, y1], [x2, y2], ... ] + // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + + var series = [], + options = { + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + legend: { + show: true, + noColumns: 1, // number of colums in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85, // set to 0 to avoid background + sorted: null // default to no legend sorting + }, + xaxis: { + show: null, // null = auto-detect, true = always, false = never + position: "bottom", // or "top" + mode: null, // null or "time" + timezone: null, // "browser" for local to the client or timezone for timezone-js + font: null, // null (derived from CSS in placeholder) or object like { size: 11, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } + color: null, // base color, labels, ticks + tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" + transform: null, // null or f: number -> number to transform axis + inverseTransform: null, // if transform is set, this should be the inverse function + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + reserveSpace: null, // whether to reserve space even if axis isn't shown + tickLength: null, // size in pixels of ticks, or "full" for whole line + alignTicksWithAxis: null, // axis number or null for no sync + + // mode specific options + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null, // number or [number, "unit"] + monthNames: null, // list of names of months + timeformat: null, // format string to use + twelveHourClock: false // 12 or 24 time in time mode + }, + yaxis: { + autoscaleMargin: 0.02, + position: "left" // or "right" + }, + xaxes: [], + yaxes: [], + series: { + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff", + symbol: "circle" // or callback + }, + lines: { + // we don't put in show: false so we can see + // whether lines were actively disabled + lineWidth: 2, // in pixels + fill: false, + fillColor: null, + steps: false + // Omit 'zero', so we can later default its value to + // match that of the 'fill' option. + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left", // "left", "right", or "center" + horizontal: false, + zero: true + }, + shadowSize: 3, + highlightColor: null + }, + grid: { + show: true, + aboveData: false, + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + borderColor: null, // set if different from the grid color + tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" + margin: 0, // distance from the canvas edge to the grid + labelMargin: 5, // in pixels + axisMargin: 8, // in pixels + borderWidth: 2, // in pixels + minBorderMargin: null, // in pixels, null means taken from points radius + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + interaction: { + redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow + }, + hooks: {} + }, + canvas = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + xaxes = [], yaxes = [], + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + canvasWidth = 0, canvasHeight = 0, + plotWidth = 0, plotHeight = 0, + hooks = { + processOptions: [], + processRawData: [], + processDatapoints: [], + processOffset: [], + drawBackground: [], + drawSeries: [], + draw: [], + bindEvents: [], + drawOverlay: [], + shutdown: [] + }, + plot = this; + + // public functions + plot.setData = setData; + plot.setupGrid = setupGrid; + plot.draw = draw; + plot.getPlaceholder = function() { return placeholder; }; + plot.getCanvas = function() { return canvas; }; + plot.getPlotOffset = function() { return plotOffset; }; + plot.width = function () { return plotWidth; }; + plot.height = function () { return plotHeight; }; + plot.offset = function () { + var o = eventHolder.offset(); + o.left += plotOffset.left; + o.top += plotOffset.top; + return o; + }; + plot.getData = function () { return series; }; + plot.getAxes = function () { + var res = {}, i; + $.each(xaxes.concat(yaxes), function (_, axis) { + if (axis) + res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; + }); + return res; + }; + plot.getXAxes = function () { return xaxes; }; + plot.getYAxes = function () { return yaxes; }; + plot.c2p = canvasToAxisCoords; + plot.p2c = axisToCanvasCoords; + plot.getOptions = function () { return options; }; + plot.highlight = highlight; + plot.unhighlight = unhighlight; + plot.triggerRedrawOverlay = triggerRedrawOverlay; + plot.pointOffset = function(point) { + return { + left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), + top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) + }; + }; + plot.shutdown = shutdown; + plot.resize = function () { + getCanvasDimensions(); + resizeCanvas(canvas); + resizeCanvas(overlay); + }; + + // public attributes + plot.hooks = hooks; + + // initialize + initPlugins(plot); + parseOptions(options_); + setupCanvases(); + setData(data_); + setupGrid(); + draw(); + bindEvents(); + + + function executeHooks(hook, args) { + args = [plot].concat(args); + for (var i = 0; i < hook.length; ++i) + hook[i].apply(this, args); + } + + function initPlugins() { + for (var i = 0; i < plugins.length; ++i) { + var p = plugins[i]; + p.init(plot); + if (p.options) + $.extend(true, options, p.options); + } + } + + function parseOptions(opts) { + var i; + + $.extend(true, options, opts); + + if (options.xaxis.color == null) + options.xaxis.color = options.grid.color; + if (options.yaxis.color == null) + options.yaxis.color = options.grid.color; + + if (options.xaxis.tickColor == null) // backwards-compatibility + options.xaxis.tickColor = options.grid.tickColor; + if (options.yaxis.tickColor == null) // backwards-compatibility + options.yaxis.tickColor = options.grid.tickColor; + + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color; + if (options.grid.tickColor == null) + options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + // fill in defaults in axes, copy at least always the + // first as the rest of the code assumes it'll be there + for (i = 0; i < Math.max(1, options.xaxes.length); ++i) + options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); + for (i = 0; i < Math.max(1, options.yaxes.length); ++i) + options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); + + // backwards compatibility, to be removed in future + if (options.xaxis.noTicks && options.xaxis.ticks == null) + options.xaxis.ticks = options.xaxis.noTicks; + if (options.yaxis.noTicks && options.yaxis.ticks == null) + options.yaxis.ticks = options.yaxis.noTicks; + if (options.x2axis) { + options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); + options.xaxes[1].position = "top"; + } + if (options.y2axis) { + options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); + options.yaxes[1].position = "right"; + } + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; + if (options.lines) + $.extend(true, options.series.lines, options.lines); + if (options.points) + $.extend(true, options.series.points, options.points); + if (options.bars) + $.extend(true, options.series.bars, options.bars); + if (options.shadowSize != null) + options.series.shadowSize = options.shadowSize; + if (options.highlightColor != null) + options.series.highlightColor = options.highlightColor; + + // save options on axes for future reference + for (i = 0; i < options.xaxes.length; ++i) + getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; + for (i = 0; i < options.yaxes.length; ++i) + getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; + + // add hooks from options + for (var n in hooks) + if (options.hooks[n] && options.hooks[n].length) + hooks[n] = hooks[n].concat(options.hooks[n]); + + executeHooks(hooks.processOptions, [options]); + } + + function setData(d) { + series = parseData(d); + fillInSeriesOptions(); + processData(); + } + + function parseData(d) { + var res = []; + for (var i = 0; i < d.length; ++i) { + var s = $.extend(true, {}, options.series); + + if (d[i].data != null) { + s.data = d[i].data; // move the data instead of deep-copy + delete d[i].data; + + $.extend(true, s, d[i]); + + d[i].data = s.data; + } + else + s.data = d[i]; + res.push(s); + } + + return res; + } + + function axisNumber(obj, coord) { + var a = obj[coord + "axis"]; + if (typeof a == "object") // if we got a real axis, extract number + a = a.n; + if (typeof a != "number") + a = 1; // default to first axis + return a; + } + + function allAxes() { + // return flat array without annoying null entries + return $.grep(xaxes.concat(yaxes), function (a) { return a; }); + } + + function canvasToAxisCoords(pos) { + // return an object with x/y corresponding to all used axes + var res = {}, i, axis; + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) + res["x" + axis.n] = axis.c2p(pos.left); + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) + res["y" + axis.n] = axis.c2p(pos.top); + } + + if (res.x1 !== undefined) + res.x = res.x1; + if (res.y1 !== undefined) + res.y = res.y1; + + return res; + } + + function axisToCanvasCoords(pos) { + // get canvas coords from the first pair of x/y found in pos + var res = {}, i, axis, key; + + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) { + key = "x" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "x"; + + if (pos[key] != null) { + res.left = axis.p2c(pos[key]); + break; + } + } + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) { + key = "y" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "y"; + + if (pos[key] != null) { + res.top = axis.p2c(pos[key]); + break; + } + } + } + + return res; + } + + function getOrCreateAxis(axes, number) { + if (!axes[number - 1]) + axes[number - 1] = { + n: number, // save the number for future reference + direction: axes == xaxes ? "x" : "y", + options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) + }; + + return axes[number - 1]; + } + + function fillInSeriesOptions() { + + var neededColors = series.length, maxIndex = -1, i; + + // Subtract the number of series that already have fixed colors or + // color indexes from the number that we still need to generate. + + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + neededColors--; + if (typeof sc == "number" && sc > maxIndex) { + maxIndex = sc; + } + } + } + + // If any of the series have fixed color indexes, then we need to + // generate at least as many colors as the highest index. + + if (neededColors <= maxIndex) { + neededColors = maxIndex + 1; + } + + // Generate all the colors, using first the option colors and then + // variations on those colors once they're exhausted. + + var c, colors = [], colorPool = options.colors, + colorPoolSize = colorPool.length, variation = 0; + + for (i = 0; i < neededColors; i++) { + + c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); + + // Each time we exhaust the colors in the pool we adjust + // a scaling factor used to produce more variations on + // those colors. The factor alternates negative/positive + // to produce lighter/darker colors. + + // Reset the variation after every few cycles, or else + // it will end up producing only white or black colors. + + if (i % colorPoolSize == 0 && i) { + if (variation >= 0) { + if (variation < 0.5) { + variation = -variation - 0.2; + } else variation = 0; + } else variation = -variation; + } + + colors[i] = c.scale('rgb', 1 + variation); + } + + // Finalize the series options, filling in their colors + + var colori = 0, s; + for (i = 0; i < series.length; ++i) { + s = series[i]; + + // assign colors + if (s.color == null) { + s.color = colors[colori].toString(); + ++colori; + } + else if (typeof s.color == "number") + s.color = colors[s.color].toString(); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null) { + var v, show = true; + for (v in s) + if (s[v] && s[v].show) { + show = false; + break; + } + if (show) + s.lines.show = true; + } + + // If nothing was provided for lines.zero, default it to match + // lines.fill, since areas by default should extend to zero. + + if (s.lines.zero == null) { + s.lines.zero = !!s.lines.fill; + } + + // setup axes + s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); + s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); + } + } + + function processData() { + var topSentry = Number.POSITIVE_INFINITY, + bottomSentry = Number.NEGATIVE_INFINITY, + fakeInfinity = Number.MAX_VALUE, + i, j, k, m, length, + s, points, ps, x, y, axis, val, f, p, + data, format; + + function updateAxis(axis, min, max) { + if (min < axis.datamin && min != -fakeInfinity) + axis.datamin = min; + if (max > axis.datamax && max != fakeInfinity) + axis.datamax = max; + } + + $.each(allAxes(), function (_, axis) { + // init axis + axis.datamin = topSentry; + axis.datamax = bottomSentry; + axis.used = false; + }); + + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [] }; + + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); + } + + // first pass: clean and copy data + for (i = 0; i < series.length; ++i) { + s = series[i]; + + data = s.data; + format = s.datapoints.format; + + if (!format) { + format = []; + // find out how to copy + format.push({ x: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + + if (s.bars.show || (s.lines.show && s.lines.fill)) { + var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); + format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); + if (s.bars.horizontal) { + delete format[format.length - 1].y; + format[format.length - 1].x = true; + } + } + + s.datapoints.format = format; + } + + if (s.datapoints.pointsize != null) + continue; // already filled in + + s.datapoints.pointsize = format.length; + + ps = s.datapoints.pointsize; + points = s.datapoints.points; + + var insertSteps = s.lines.show && s.lines.steps; + s.xaxis.used = s.yaxis.used = true; + + for (j = k = 0; j < data.length; ++j, k += ps) { + p = data[j]; + + var nullify = p == null; + if (!nullify) { + for (m = 0; m < ps; ++m) { + val = p[m]; + f = format[m]; + + if (f) { + if (f.number && val != null) { + val = +val; // convert to number + if (isNaN(val)) + val = null; + else if (val == Infinity) + val = fakeInfinity; + else if (val == -Infinity) + val = -fakeInfinity; + } + + if (val == null) { + if (f.required) + nullify = true; + + if (f.defaultValue != null) + val = f.defaultValue; + } + } + + points[k + m] = val; + } + } + + if (nullify) { + for (m = 0; m < ps; ++m) { + val = points[k + m]; + if (val != null) { + f = format[m]; + // extract min/max info + if (f.x) + updateAxis(s.xaxis, val, val); + if (f.y) + updateAxis(s.yaxis, val, val); + } + points[k + m] = null; + } + } + else { + // a little bit of line specific stuff that + // perhaps shouldn't be here, but lacking + // better means... + if (insertSteps && k > 0 + && points[k - ps] != null + && points[k - ps] != points[k] + && points[k - ps + 1] != points[k + 1]) { + // copy the point to make room for a middle point + for (m = 0; m < ps; ++m) + points[k + ps + m] = points[k + m]; + + // middle point has same y + points[k + 1] = points[k - ps + 1]; + + // we've added a point, better reflect that + k += ps; + } + } + } + } + + // give the hooks a chance to run + for (i = 0; i < series.length; ++i) { + s = series[i]; + + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); + } + + // second pass: find datamax/datamin for auto-scaling + for (i = 0; i < series.length; ++i) { + s = series[i]; + points = s.datapoints.points, + ps = s.datapoints.pointsize; + format = s.datapoints.format; + + var xmin = topSentry, ymin = topSentry, + xmax = bottomSentry, ymax = bottomSentry; + + for (j = 0; j < points.length; j += ps) { + if (points[j] == null) + continue; + + for (m = 0; m < ps; ++m) { + val = points[j + m]; + f = format[m]; + if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) + continue; + + if (f.x) { + if (val < xmin) + xmin = val; + if (val > xmax) + xmax = val; + } + if (f.y) { + if (val < ymin) + ymin = val; + if (val > ymax) + ymax = val; + } + } + } + + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta; + + switch (s.bars.align) { + case "left": + delta = 0; + break; + case "right": + delta = -s.bars.barWidth; + break; + case "center": + delta = -s.bars.barWidth / 2; + break; + default: + throw new Error("Invalid bar alignment: " + s.bars.align); + } + + if (s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + updateAxis(s.xaxis, xmin, xmax); + updateAxis(s.yaxis, ymin, ymax); + } + + $.each(allAxes(), function (_, axis) { + if (axis.datamin == topSentry) + axis.datamin = null; + if (axis.datamax == bottomSentry) + axis.datamax = null; + }); + } + + ////////////////////////////////////////////////////////////////////////////////// + // Returns the display's ratio between physical and device-independent pixels. + // + // This is the ratio between the width that the browser advertises and the number + // of pixels actually available in that space. The iPhone 4, for example, has a + // device-independent width of 320px, but its screen is actually 640px wide. It + // therefore has a pixel ratio of 2, while most normal devices have a ratio of 1. + + function getPixelRatio(cctx) { + var devicePixelRatio = window.devicePixelRatio || 1; + var backingStoreRatio = + cctx.webkitBackingStorePixelRatio || + cctx.mozBackingStorePixelRatio || + cctx.msBackingStorePixelRatio || + cctx.oBackingStorePixelRatio || + cctx.backingStorePixelRatio || 1; + + return devicePixelRatio / backingStoreRatio; + } + + function makeCanvas(cls) { + + var c = document.createElement('canvas'); + c.className = cls; + + $(c).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) + .appendTo(placeholder); + + // If HTML5 Canvas isn't available, fall back to Excanvas + + if (!c.getContext) { + if (window.G_vmlCanvasManager) { + c = window.G_vmlCanvasManager.initElement(c); + } else { + throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); + } + } + + var cctx = c.getContext("2d"); + + // Increase the canvas density based on the display's pixel ratio; basically + // giving the canvas more pixels without increasing the size of its element, + // to take advantage of the fact that retina displays have that many more + // pixels than they actually use for page & element widths. + + var pixelRatio = getPixelRatio(cctx); + + c.width = canvasWidth * pixelRatio; + c.height = canvasHeight * pixelRatio; + c.style.width = canvasWidth + "px"; + c.style.height = canvasHeight + "px"; + + // Save the context so we can reset in case we get replotted + + cctx.save(); + + // Scale the coordinate space to match the display density; so even though we + // may have twice as many pixels, we still want lines and other drawing to + // appear at the same size; the extra pixels will just make them crisper. + + cctx.scale(pixelRatio, pixelRatio); + + return c; + } + + function getCanvasDimensions() { + canvasWidth = placeholder.width(); + canvasHeight = placeholder.height(); + + if (canvasWidth <= 0 || canvasHeight <= 0) + throw new Error("Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight); + } + + function resizeCanvas(c) { + + var cctx = c.getContext("2d"); + + // Handle pixel ratios > 1 for retina displays, as explained in makeCanvas + + var pixelRatio = getPixelRatio(cctx); + + // Resizing should reset the state (excanvas seems to be buggy though) + + if (c.style.width != canvasWidth) { + c.width = canvasWidth * pixelRatio; + c.style.width = canvasWidth + "px"; + } + + if (c.style.height != canvasHeight) { + c.height = canvasHeight * pixelRatio; + c.style.height = canvasHeight + "px"; + } + + // so try to get back to the initial state (even if it's + // gone now, this should be safe according to the spec) + cctx.restore(); + + // and save again + cctx.save(); + + // Apply scaling for retina displays, as explained in makeCanvas + + cctx.scale(pixelRatio, pixelRatio); + } + + function setupCanvases() { + var reused, + existingCanvas = placeholder.children("canvas.flot-base"), + existingOverlay = placeholder.children("canvas.flot-overlay"); + + if (existingCanvas.length == 0 || existingOverlay == 0) { + // init everything + + placeholder.html(""); // make sure placeholder is clear + + placeholder.css({ padding: 0 }); // padding messes up the positioning + + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + getCanvasDimensions(); + + canvas = makeCanvas("flot-base"); + overlay = makeCanvas("flot-overlay"); // overlay canvas for interactive features + + reused = false; + } + else { + // reuse existing elements + + canvas = existingCanvas.get(0); + overlay = existingOverlay.get(0); + + reused = true; + } + + ctx = canvas.getContext("2d"); + octx = overlay.getContext("2d"); + + // define which element we're listening for events on + eventHolder = $(overlay); + + if (reused) { + // run shutdown in the old plot object + placeholder.data("plot").shutdown(); + + // reset reused canvases + plot.resize(); + + // make sure overlay pixels are cleared (canvas is cleared when we redraw) + octx.clearRect(0, 0, canvasWidth, canvasHeight); + + // then whack any remaining obvious garbage left + eventHolder.unbind(); + placeholder.children().not([canvas, overlay]).remove(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { + // bind events + if (options.grid.hoverable) { + eventHolder.mousemove(onMouseMove); + + // Use bind, rather than .mouseleave, because we officially + // still support jQuery 1.2.6, which doesn't define a shortcut + // for mouseenter or mouseleave. This was a bug/oversight that + // was fixed somewhere around 1.3.x. We can return to using + // .mouseleave when we drop support for 1.2.6. + + eventHolder.bind("mouseleave", onMouseLeave); + } + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("click", onClick); + + executeHooks(hooks.shutdown, [eventHolder]); + } + + function setTransformationHelpers(axis) { + // set helper functions on the axis, assumes plot area + // has been computed already + + function identity(x) { return x; } + + var s, m, t = axis.options.transform || identity, + it = axis.options.inverseTransform; + + // precompute how much the axis is scaling a point + // in canvas space + if (axis.direction == "x") { + s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); + m = Math.min(t(axis.max), t(axis.min)); + } + else { + s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); + s = -s; + m = Math.max(t(axis.max), t(axis.min)); + } + + // data point to canvas coordinate + if (t == identity) // slight optimization + axis.p2c = function (p) { return (p - m) * s; }; + else + axis.p2c = function (p) { return (t(p) - m) * s; }; + // canvas coordinate to data point + if (!it) + axis.c2p = function (c) { return m + c / s; }; + else + axis.c2p = function (c) { return it(m + c / s); }; + } + + function measureTickLabels(axis) { + var opts = axis.options, ticks = axis.ticks || [], + axisw = opts.labelWidth || 0, axish = opts.labelHeight || 0, + f = axis.font; + + ctx.save(); + ctx.font = f.style + " " + f.variant + " " + f.weight + " " + f.size + "px '" + f.family + "'"; + + for (var i = 0; i < ticks.length; ++i) { + var t = ticks[i]; + + t.lines = []; + t.width = t.height = 0; + + if (!t.label) + continue; + + // accept various kinds of newlines, including HTML ones + // (you can actually split directly on regexps in Javascript, + // but IE < 9 is unfortunately broken) + var lines = (t.label + "").replace(/
    |\r\n|\r/g, "\n").split("\n"); + for (var j = 0; j < lines.length; ++j) { + var line = { text: lines[j] }, + m = ctx.measureText(line.text); + + line.width = m.width; + // m.height might not be defined, not in the + // standard yet + line.height = m.height != null ? m.height : f.size; + + // add a bit of margin since font rendering is + // not pixel perfect and cut off letters look + // bad, this also doubles as spacing between + // lines + line.height += Math.round(f.size * 0.15); + + t.width = Math.max(line.width, t.width); + t.height += line.height; + + t.lines.push(line); + } + + if (opts.labelWidth == null) + axisw = Math.max(axisw, t.width); + if (opts.labelHeight == null) + axish = Math.max(axish, t.height); + } + ctx.restore(); + + axis.labelWidth = Math.ceil(axisw); + axis.labelHeight = Math.ceil(axish); + } + + function allocateAxisBoxFirstPhase(axis) { + // find the bounding box of the axis by looking at label + // widths/heights and ticks, make room by diminishing the + // plotOffset; this first phase only looks at one + // dimension per axis, the other dimension depends on the + // other axes so will have to wait + + var lw = axis.labelWidth, + lh = axis.labelHeight, + pos = axis.options.position, + tickLength = axis.options.tickLength, + axisMargin = options.grid.axisMargin, + padding = options.grid.labelMargin, + all = axis.direction == "x" ? xaxes : yaxes, + index, innermost; + + // determine axis margin + var samePosition = $.grep(all, function (a) { + return a && a.options.position == pos && a.reserveSpace; + }); + if ($.inArray(axis, samePosition) == samePosition.length - 1) + axisMargin = 0; // outermost + + // determine tick length - if we're innermost, we can use "full" + if (tickLength == null) { + var sameDirection = $.grep(all, function (a) { + return a && a.reserveSpace; + }); + + innermost = $.inArray(axis, sameDirection) == 0; + if (innermost) + tickLength = "full"; + else + tickLength = 5; + } + + if (!isNaN(+tickLength)) + padding += +tickLength; + + // compute box + if (axis.direction == "x") { + lh += padding; + + if (pos == "bottom") { + plotOffset.bottom += lh + axisMargin; + axis.box = { top: canvasHeight - plotOffset.bottom, height: lh }; + } + else { + axis.box = { top: plotOffset.top + axisMargin, height: lh }; + plotOffset.top += lh + axisMargin; + } + } + else { + lw += padding; + + if (pos == "left") { + axis.box = { left: plotOffset.left + axisMargin, width: lw }; + plotOffset.left += lw + axisMargin; + } + else { + plotOffset.right += lw + axisMargin; + axis.box = { left: canvasWidth - plotOffset.right, width: lw }; + } + } + + // save for future reference + axis.position = pos; + axis.tickLength = tickLength; + axis.box.padding = padding; + axis.innermost = innermost; + } + + function allocateAxisBoxSecondPhase(axis) { + // now that all axis boxes have been placed in one + // dimension, we can set the remaining dimension coordinates + if (axis.direction == "x") { + axis.box.left = plotOffset.left - axis.labelWidth / 2; + axis.box.width = canvasWidth - plotOffset.left - plotOffset.right + axis.labelWidth; + } + else { + axis.box.top = plotOffset.top - axis.labelHeight / 2; + axis.box.height = canvasHeight - plotOffset.bottom - plotOffset.top + axis.labelHeight; + } + } + + function adjustLayoutForThingsStickingOut() { + // possibly adjust plot offset to ensure everything stays + // inside the canvas and isn't clipped off + + var minMargin = options.grid.minBorderMargin, + margins = { x: 0, y: 0 }, i, axis; + + // check stuff from the plot (FIXME: this should just read + // a value from the series, otherwise it's impossible to + // customize) + if (minMargin == null) { + minMargin = 0; + for (i = 0; i < series.length; ++i) + minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); + } + + margins.x = margins.y = Math.ceil(minMargin); + + // check axis labels, note we don't check the actual + // labels but instead use the overall width/height to not + // jump as much around with replots + $.each(allAxes(), function (_, axis) { + var dir = axis.direction; + if (axis.reserveSpace) + margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2)); + }); + + plotOffset.left = Math.max(margins.x, plotOffset.left); + plotOffset.right = Math.max(margins.x, plotOffset.right); + plotOffset.top = Math.max(margins.y, plotOffset.top); + plotOffset.bottom = Math.max(margins.y, plotOffset.bottom); + } + + function setupGrid() { + var i, axes = allAxes(), showGrid = options.grid.show; + + // Initialize the plot's offset from the edge of the canvas + + for (var a in plotOffset) { + var margin = options.grid.margin || 0; + plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; + } + + executeHooks(hooks.processOffset, [plotOffset]); + + // If the grid is visible, add its border width to the offset + + for (var a in plotOffset) { + if(typeof(options.grid.borderWidth) == "object") { + plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; + } + else { + plotOffset[a] += showGrid ? options.grid.borderWidth : 0; + } + } + + // init axes + $.each(axes, function (_, axis) { + axis.show = axis.options.show; + if (axis.show == null) + axis.show = axis.used; // by default an axis is visible if it's got data + + axis.reserveSpace = axis.show || axis.options.reserveSpace; + + setRange(axis); + }); + + if (showGrid) { + // determine from the placeholder the font size ~ height of font ~ 1 em + var fontDefaults = { + style: placeholder.css("font-style"), + size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)), + variant: placeholder.css("font-variant"), + weight: placeholder.css("font-weight"), + family: placeholder.css("font-family") + }; + + var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); + + $.each(allocatedAxes, function (_, axis) { + // make the ticks + setupTickGeneration(axis); + setTicks(axis); + snapRangeToTicks(axis, axis.ticks); + + // find labelWidth/Height for axis + axis.font = $.extend({}, fontDefaults, axis.options.font); + measureTickLabels(axis); + }); + + // with all dimensions calculated, we can compute the + // axis bounding boxes, start from the outside + // (reverse order) + for (i = allocatedAxes.length - 1; i >= 0; --i) + allocateAxisBoxFirstPhase(allocatedAxes[i]); + + // make sure we've got enough space for things that + // might stick out + adjustLayoutForThingsStickingOut(); + + $.each(allocatedAxes, function (_, axis) { + allocateAxisBoxSecondPhase(axis); + }); + } + + plotWidth = canvasWidth - plotOffset.left - plotOffset.right; + plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; + + // now we got the proper plot dimensions, we can compute the scaling + $.each(axes, function (_, axis) { + setTransformationHelpers(axis); + }); + + insertLegend(); + } + + function setRange(axis) { + var opts = axis.options, + min = +(opts.min != null ? opts.min : axis.datamin), + max = +(opts.max != null ? opts.max : axis.datamax), + delta = max - min; + + if (delta == 0.0) { + // degenerate case + var widen = max == 0 ? 1 : 0.01; + + if (opts.min == null) + min -= widen; + // always widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (opts.max == null || opts.min != null) + max += widen; + } + else { + // consider autoscaling + var margin = opts.autoscaleMargin; + if (margin != null) { + if (opts.min == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && axis.datamin != null && axis.datamin >= 0) + min = 0; + } + if (opts.max == null) { + max += delta * margin; + if (max > 0 && axis.datamax != null && axis.datamax <= 0) + max = 0; + } + } + } + axis.min = min; + axis.max = max; + } + + function setupTickGeneration(axis) { + var opts = axis.options; + + // estimate number of ticks + var noTicks; + if (typeof opts.ticks == "number" && opts.ticks > 0) + noTicks = opts.ticks; + else + // heuristic based on the model a*sqrt(x) fitted to + // some data points that seemed reasonable + noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight); + + axis.delta = (axis.max - axis.min) / noTicks; + + // Time mode was moved to a plug-in in 0.8, but since so many people use this + // we'll add an especially friendly make sure they remembered to include it. + + if (opts.mode == "time" && !axis.tickGenerator) { + throw new Error("Time mode requires the flot.time plugin."); + } + + // Flot supports base-10 axes; any other mode else is handled by a plug-in, + // like flot.time.js. + + if (!axis.tickGenerator) { + + axis.tickGenerator = function (axis) { + var maxDec = opts.tickDecimals, + dec = -Math.floor(Math.log(axis.delta) / Math.LN10); + + if (maxDec != null && dec > maxDec) + dec = maxDec; + + var magn = Math.pow(10, -dec), + norm = axis.delta / magn, // norm is between 1.0 and 10.0 + size, + + ticks = [], + start, + i = 0, + v = Number.NaN, + prev; + + if (norm < 1.5) + size = 1; + else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } + else if (norm < 7.5) + size = 5; + else size = 10; + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) + size = opts.minTickSize; + + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; + + start = floorInBase(axis.min, axis.tickSize) + + do { + prev = v; + v = start + i * axis.tickSize; + ticks.push(v); + ++i; + } while (v < axis.max && v != prev); + return ticks; + }; + + axis.tickFormatter = function (value, axis) { + + var factor = Math.pow(10, axis.tickDecimals); + var formatted = "" + Math.round(value * factor) / factor; + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (axis.tickDecimals != null) { + var decimal = formatted.indexOf("."); + var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.tickDecimals) { + return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); + } + } + + return formatted; + }; + } + + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + + if (opts.alignTicksWithAxis != null) { + var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; + if (otherAxis && otherAxis.used && otherAxis != axis) { + // consider snapping min/max to outermost nice ticks + var niceTicks = axis.tickGenerator(axis); + if (niceTicks.length > 0) { + if (opts.min == null) + axis.min = Math.min(axis.min, niceTicks[0]); + if (opts.max == null && niceTicks.length > 1) + axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); + } + + axis.tickGenerator = function (axis) { + // copy ticks, scaled to this axis + var ticks = [], v, i; + for (i = 0; i < otherAxis.ticks.length; ++i) { + v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); + v = axis.min + v * (axis.max - axis.min); + ticks.push(v); + } + return ticks; + }; + + // we might need an extra decimal since forced + // ticks don't necessarily fit naturally + if (!axis.mode && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), + ts = axis.tickGenerator(axis); + + // only proceed if the tick interval rounded + // with an extra decimal doesn't give us a + // zero at end + if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) + axis.tickDecimals = extraDec; + } + } + } + } + + function setTicks(axis) { + var oticks = axis.options.ticks, ticks = []; + if (oticks == null || (typeof oticks == "number" && oticks > 0)) + ticks = axis.tickGenerator(axis); + else if (oticks) { + if ($.isFunction(oticks)) + // generate the ticks + ticks = oticks(axis); + else + ticks = oticks; + } + + // clean up/labelify the supplied ticks, copy them over + var i, v; + axis.ticks = []; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = +t[0]; + if (t.length > 1) + label = t[1]; + } + else + v = +t; + if (label == null) + label = axis.tickFormatter(v, axis); + if (!isNaN(v)) + axis.ticks.push({ v: v, label: label }); + } + } + + function snapRangeToTicks(axis, ticks) { + if (axis.options.autoscaleMargin && ticks.length > 0) { + // snap to ticks + if (axis.options.min == null) + axis.min = Math.min(axis.min, ticks[0].v); + if (axis.options.max == null && ticks.length > 1) + axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); + } + } + + function draw() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + executeHooks(hooks.drawBackground, [ctx]); + + var grid = options.grid; + + // draw background, if any + if (grid.show && grid.backgroundColor) + drawBackground(); + + if (grid.show && !grid.aboveData) { + drawGrid(); + drawAxisLabels(); + } + + for (var i = 0; i < series.length; ++i) { + executeHooks(hooks.drawSeries, [ctx, series[i]]); + drawSeries(series[i]); + } + + executeHooks(hooks.draw, [ctx]); + + if (grid.show && grid.aboveData) { + drawGrid(); + drawAxisLabels(); + } + } + + function extractRange(ranges, coord) { + var axis, from, to, key, axes = allAxes(); + + for (var i = 0; i < axes.length; ++i) { + axis = axes[i]; + if (axis.direction == coord) { + key = coord + axis.n + "axis"; + if (!ranges[key] && axis.n == 1) + key = coord + "axis"; // support x1axis as xaxis + if (ranges[key]) { + from = ranges[key].from; + to = ranges[key].to; + break; + } + } + } + + // backwards-compat stuff - to be removed in future + if (!ranges[key]) { + axis = coord == "x" ? xaxes[0] : yaxes[0]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) { + var tmp = from; + from = to; + to = tmp; + } + + return { from: from, to: to, axis: axis }; + } + + function drawBackground() { + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); + ctx.fillRect(0, 0, plotWidth, plotHeight); + ctx.restore(); + } + + function drawGrid() { + var i, axes, bw, bc; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) { + axes = plot.getAxes(); + // xmin etc. is backwards compatibility, to be + // removed in the future + axes.xmin = axes.xaxis.min; + axes.xmax = axes.xaxis.max; + axes.ymin = axes.yaxis.min; + axes.ymax = axes.yaxis.max; + + markings = markings(axes); + } + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; + + // clip + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); + + if (xrange.from == xrange.to && yrange.from == yrange.to) + continue; + + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the ticks + axes = allAxes(); + bw = options.grid.borderWidth; + + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box, + t = axis.tickLength, x, y, xoff, yoff; + if (!axis.show || axis.ticks.length == 0) + continue; + + ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); + ctx.lineWidth = 1; + + // find the edges + if (axis.direction == "x") { + x = 0; + if (t == "full") + y = (axis.position == "top" ? 0 : plotHeight); + else + y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); + } + else { + y = 0; + if (t == "full") + x = (axis.position == "left" ? 0 : plotWidth); + else + x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); + } + + // draw tick bar + if (!axis.innermost) { + ctx.beginPath(); + xoff = yoff = 0; + if (axis.direction == "x") + xoff = plotWidth; + else + yoff = plotHeight; + + if (ctx.lineWidth == 1) { + x = Math.floor(x) + 0.5; + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + ctx.stroke(); + } + + // draw ticks + ctx.beginPath(); + for (i = 0; i < axis.ticks.length; ++i) { + var v = axis.ticks[i].v; + + xoff = yoff = 0; + + if (v < axis.min || v > axis.max + // skip those lying on the axes if we got a border + || (t == "full" + && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) + && (v == axis.min || v == axis.max))) + continue; + + if (axis.direction == "x") { + x = axis.p2c(v); + yoff = t == "full" ? -plotHeight : t; + + if (axis.position == "top") + yoff = -yoff; + } + else { + y = axis.p2c(v); + xoff = t == "full" ? -plotWidth : t; + + if (axis.position == "left") + xoff = -xoff; + } + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") + x = Math.floor(x) + 0.5; + else + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + } + + ctx.stroke(); + } + + + // draw border + if (bw) { + // If either borderWidth or borderColor is an object, then draw the border + // line by line instead of as one rectangle + bc = options.grid.borderColor; + if(typeof bw == "object" || typeof bc == "object") { + if (typeof bw !== "object") { + bw = {top: bw, right: bw, bottom: bw, left: bw}; + } + if (typeof bc !== "object") { + bc = {top: bc, right: bc, bottom: bc, left: bc}; + } + + if (bw.top > 0) { + ctx.strokeStyle = bc.top; + ctx.lineWidth = bw.top; + ctx.beginPath(); + ctx.moveTo(0 - bw.left, 0 - bw.top/2); + ctx.lineTo(plotWidth, 0 - bw.top/2); + ctx.stroke(); + } + + if (bw.right > 0) { + ctx.strokeStyle = bc.right; + ctx.lineWidth = bw.right; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); + ctx.lineTo(plotWidth + bw.right / 2, plotHeight); + ctx.stroke(); + } + + if (bw.bottom > 0) { + ctx.strokeStyle = bc.bottom; + ctx.lineWidth = bw.bottom; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); + ctx.lineTo(0, plotHeight + bw.bottom / 2); + ctx.stroke(); + } + + if (bw.left > 0) { + ctx.strokeStyle = bc.left; + ctx.lineWidth = bw.left; + ctx.beginPath(); + ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); + ctx.lineTo(0- bw.left/2, 0); + ctx.stroke(); + } + } + else { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + } + + ctx.restore(); + } + + function drawAxisLabels() { + ctx.save(); + + $.each(allAxes(), function (_, axis) { + if (!axis.show || axis.ticks.length == 0) + return; + + var box = axis.box, f = axis.font; + // placeholder.append('
    ') // debug + + ctx.fillStyle = axis.options.color; + // Important: Don't use quotes around axis.font.family! Just around single + // font names like 'Times New Roman' that have a space or special character in it. + ctx.font = f.style + " " + f.variant + " " + f.weight + " " + f.size + "px " + f.family; + ctx.textAlign = "start"; + // middle align the labels - top would be more + // natural, but browsers can differ a pixel or two in + // where they consider the top to be, so instead we + // middle align to minimize variation between browsers + // and compensate when calculating the coordinates + ctx.textBaseline = "middle"; + + for (var i = 0; i < axis.ticks.length; ++i) { + var tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + + var x, y, offset = 0, line; + for (var k = 0; k < tick.lines.length; ++k) { + line = tick.lines[k]; + + if (axis.direction == "x") { + x = plotOffset.left + axis.p2c(tick.v) - line.width/2; + if (axis.position == "bottom") + y = box.top + box.padding; + else + y = box.top + box.height - box.padding - tick.height; + } + else { + y = plotOffset.top + axis.p2c(tick.v) - tick.height/2; + if (axis.position == "left") + x = box.left + box.width - box.padding - line.width; + else + x = box.left + box.padding; + } + + // account for middle aligning and line number + y += line.height/2 + offset; + offset += line.height; + + if (!!(window.opera && window.opera.version().split('.')[0] < 12)) { + // FIXME: LEGACY BROWSER FIX + // AFFECTS: Opera < 12.00 + + // round the coordinates since Opera + // otherwise switches to more ugly + // rendering (probably non-hinted) and + // offset the y coordinates since it seems + // to be off pretty consistently compared + // to the other browsers + x = Math.floor(x); + y = Math.ceil(y - 2); + } + ctx.fillText(line.text, x, y); + } + } + }); + + ctx.restore(); + } + + function drawSeries(series) { + if (series.lines.show) + drawSeriesLines(series); + if (series.bars.show) + drawSeriesBars(series); + if (series.points.show) + drawSeriesPoints(series); + } + + function drawSeriesLines(series) { + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + prevx = null, prevy = null; + + ctx.beginPath(); + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1], + x2 = points[i], y2 = points[i + 1]; + + if (x1 == null || x2 == null) + continue; + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min) { + if (y2 < axisy.min) + continue; // line segment is outside + // compute new intersection point + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min) { + if (y1 < axisy.min) + continue; + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max) { + if (y2 > axisy.max) + continue; + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max) { + if (y1 > axisy.max) + continue; + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (x1 != prevx || y1 != prevy) + ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); + } + ctx.stroke(); + } + + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + i = 0, top, areaOpen = false, + ypos = 1, segmentStart = 0, segmentEnd = 0; + + // we process each segment in two turns, first forward + // direction to sketch out top, then once we hit the + // end we go backwards to sketch the bottom + while (true) { + if (ps > 0 && i > points.length + ps) + break; + + i += ps; // ps is negative if going backwards + + var x1 = points[i - ps], + y1 = points[i - ps + ypos], + x2 = points[i], y2 = points[i + ypos]; + + if (areaOpen) { + if (ps > 0 && x1 != null && x2 == null) { + // at turning point + segmentEnd = i; + ps = -ps; + ypos = 2; + continue; + } + + if (ps < 0 && i == segmentStart + ps) { + // done with the reverse sweep + ctx.fill(); + areaOpen = false; + ps = -ps; + ypos = 1; + i = segmentStart = segmentEnd + ps; + continue; + } + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + continue; + } + + // else it's a bit more complicated, there might + // be a flat maxed out rectangle first, then a + // triangular cutout or reverse; to find these + // keep track of the current x values + var x1old = x1, x2old = x2; + + // clip the y values, without shortcutting, we + // go through all cases in turn + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); + // it goes to (x1, y1), but we fill that below + } + + // fill triangular section, this sometimes result + // in redundant points if (x1, y1) hasn't changed + // from previous line to, but we just ignore that + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); + } + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + ctx.lineJoin = "round"; + + var lw = series.lines.lineWidth, + sw = series.shadowSize; + // FIXME: consider another form of shadow when filling is turned on + if (lw > 0 && sw > 0) { + // draw shadow as a thick and thin line with transparency + ctx.lineWidth = sw; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + // position shadow at angle from the mid of line + var angle = Math.PI/18; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawSeriesPoints(series) { + function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + continue; + + ctx.beginPath(); + x = axisx.p2c(x); + y = axisy.p2c(y) + offset; + if (symbol == "circle") + ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); + else + symbol(ctx, x, y, radius, shadow); + ctx.closePath(); + + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + ctx.stroke(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var lw = series.points.lineWidth, + sw = series.shadowSize, + radius = series.points.radius, + symbol = series.points.symbol; + if (lw > 0 && sw > 0) { + // draw shadow in two steps + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + plotPoints(series.datapoints, radius, null, w + w/2, true, + series.xaxis, series.yaxis, symbol); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + plotPoints(series.datapoints, radius, null, w/2, true, + series.xaxis, series.yaxis, symbol); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, false, + series.xaxis, series.yaxis, symbol); + ctx.restore(); + } + + function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + // in horizontal mode, we start the bar from the left + // instead of from the bottom so it appears to be + // horizontal rather than vertical + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } + } + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + + // clip + if (right < axisx.min || left > axisx.max || + top < axisy.min || bottom > axisy.max) + return; + + if (left < axisx.min) { + left = axisx.min; + drawLeft = false; + } + + if (right > axisx.max) { + right = axisx.max; + drawRight = false; + } + + if (bottom < axisy.min) { + bottom = axisy.min; + drawBottom = false; + } + + if (top > axisy.max) { + top = axisy.max; + drawTop = false; + } + + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + + // fill the bar + if (fillStyleCallback) { + c.beginPath(); + c.moveTo(left, bottom); + c.lineTo(left, top); + c.lineTo(right, top); + c.lineTo(right, bottom); + c.fillStyle = fillStyleCallback(bottom, top); + c.fill(); + } + + // draw outline + if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom + offset); + if (drawLeft) + c.lineTo(left, top + offset); + else + c.moveTo(left, top + offset); + if (drawTop) + c.lineTo(right, top + offset); + else + c.moveTo(right, top + offset); + if (drawRight) + c.lineTo(right, bottom + offset); + else + c.moveTo(right, bottom + offset); + if (drawBottom) + c.lineTo(left, bottom + offset); + else + c.moveTo(left, bottom + offset); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + if (points[i] == null) + continue; + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // FIXME: figure out a way to add shadows (for instance along the right edge) + ctx.lineWidth = series.bars.lineWidth; + ctx.strokeStyle = series.color; + + var barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + case "center": + barLeft = -series.bars.barWidth / 2; + break; + default: + throw new Error("Invalid bar alignment: " + series.bars.align); + } + + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); + ctx.restore(); + } + + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; + if (!fill) + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + + var c = $.color.parse(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); + } + + function insertLegend() { + + placeholder.find(".legend").remove(); + + if (!options.legend.show) + return; + + var fragments = [], entries = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + + // Build a list of legend entries, with each having a label and a color + + for (var i = 0; i < series.length; ++i) { + s = series[i]; + if (s.label) { + label = lf ? lf(s.label, s) : s.label; + if (label) { + entries.push({ + label: label, + color: s.color + }); + } + } + } + + // Sort the legend using either the default or a custom comparator + + if (options.legend.sorted) { + if ($.isFunction(options.legend.sorted)) { + entries.sort(options.legend.sorted); + } else { + var ascending = options.legend.sorted != "descending"; + entries.sort(function(a, b) { + return a.label == b.label ? 0 : ( + (a.label < b.label) != ascending ? 1 : -1 // Logical XOR + ); + }); + } + } + + // Generate markup for the list of entries, in their final order + + for (var i = 0; i < entries.length; ++i) { + + var entry = entries[i]; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push(''); + fragments.push('
    '); + rowStarted = true; + } + + fragments.push( + '' + + '' + ); + } + + if (rowStarted) + fragments.push(''); + + if (fragments.length == 0) + return; + + var table = '
    ' + entry.label + '
    ' + fragments.join("") + '
    '; + if (options.legend.container != null) + $(options.legend.container).html(table); + else { + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; + if (p.charAt(0) == "n") + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; + else if (p.charAt(0) == "s") + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; + if (p.charAt(1) == "e") + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; + else if (p.charAt(1) == "w") + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; + var legend = $('
    ' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
    ').appendTo(placeholder); + if (options.legend.backgroundOpacity != 0.0) { + // put in the transparent background + // separately to avoid blended labels and + // label boxes + var c = options.legend.backgroundColor; + if (c == null) { + c = options.grid.backgroundColor; + if (c && typeof c == "string") + c = $.color.parse(c); + else + c = $.color.extract(legend, 'background-color'); + c.a = 1; + c = c.toString(); + } + var div = legend.children(); + $('
    ').prependTo(legend).css('opacity', options.legend.backgroundOpacity); + } + } + } + + + // interactive features + + var highlights = [], + redrawTimeout = null; + + // returns the data item the mouse is over, or null if none is found + function findNearbyItem(mouseX, mouseY, seriesFilter) { + var maxDistance = options.grid.mouseActiveRadius, + smallestDistance = maxDistance * maxDistance + 1, + item = null, foundPoint = false, i, j, ps; + + for (i = series.length - 1; i >= 0; --i) { + if (!seriesFilter(series[i])) + continue; + + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + ps = s.datapoints.pointsize; + // with inverse transforms, we can't use the maxx/maxy + // optimization, sadly + if (axisx.options.inverseTransform) + maxx = Number.MAX_VALUE; + if (axisy.options.inverseTransform) + maxy = Number.MAX_VALUE; + + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + + // For points and lines, the cursor must be within a + // certain distance to the data point + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) + continue; + + // We have to calculate distances in pixels, not in + // data units, because the scales of the axes may be different + var dx = Math.abs(axisx.p2c(x) - mouseX), + dy = Math.abs(axisy.p2c(y) - mouseY), + dist = dx * dx + dy * dy; // we save the sqrt + + // use <= to ensure last point takes precedence + // (last generally means on top of) + if (dist < smallestDistance) { + smallestDistance = dist; + item = [i, j / ps]; + } + } + } + + if (s.bars.show && !item) { // no other point can be nearby + var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / ps]; + } + } + } + + if (item) { + i = item[0]; + j = item[1]; + ps = series[i].datapoints.pointsize; + + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), + dataIndex: j, + series: series[i], + seriesIndex: i }; + } + + return null; + } + + function onMouseMove(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return s["hoverable"] != false; }); + } + + function onMouseLeave(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return false; }); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); + } + + // trigger click or hover event (they send the same parameters + // so we share their code) + function triggerClickHoverEvent(eventname, event, seriesFilter) { + var offset = eventHolder.offset(), + canvasX = event.pageX - offset.left - plotOffset.left, + canvasY = event.pageY - offset.top - plotOffset.top, + pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); + + pos.pageX = event.pageX; + pos.pageY = event.pageY; + + var item = findNearbyItem(canvasX, canvasY, seriesFilter); + + if (item) { + // fill in mouse pos for any listeners out there + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); + } + + 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 && + h.point[0] == item.datapoint[0] && + h.point[1] == item.datapoint[1])) + unhighlight(h.series, h.point); + } + + if (item) + highlight(item.series, item.datapoint, eventname); + } + + placeholder.trigger(eventname, [ pos, item ]); + } + + function triggerRedrawOverlay() { + var t = options.interaction.redrawOverlayInterval; + if (t == -1) { // skip event queue + drawOverlay(); + return; + } + + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, t); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + octx.clearRect(0, 0, canvasWidth, canvasHeight); + octx.translate(plotOffset.left, plotOffset.top); + + var i, hi; + for (i = 0; i < highlights.length; ++i) { + hi = highlights[i]; + + if (hi.series.bars.show) + drawBarHighlight(hi.series, hi.point); + else + drawPointHighlight(hi.series, hi.point); + } + octx.restore(); + + executeHooks(hooks.drawOverlay, [octx]); + } + + function highlight(s, point, auto) { + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i == -1) { + highlights.push({ series: s, point: point, auto: auto }); + + triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s, point) { + if (s == null && point == null) { + highlights = []; + triggerRedrawOverlay(); + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") + point = s.data[point]; + + var i = indexOfHighlight(s, point); + if (i != -1) { + highlights.splice(i, 1); + + triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s, p) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s && h.point[0] == p[0] + && h.point[1] == p[1]) + return i; + } + return -1; + } + + function drawPointHighlight(series, point) { + var x = point[0], y = point[1], + axisx = series.xaxis, axisy = series.yaxis, + highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); + + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + return; + + var pointRadius = series.points.radius + series.points.lineWidth / 2; + octx.lineWidth = pointRadius; + octx.strokeStyle = highlightColor; + var radius = 1.5 * pointRadius; + x = axisx.p2c(x); + y = axisy.p2c(y); + + octx.beginPath(); + if (series.points.symbol == "circle") + octx.arc(x, y, radius, 0, 2 * Math.PI, false); + else + series.points.symbol(octx, x, y, radius, false); + octx.closePath(); + octx.stroke(); + } + + function drawBarHighlight(series, point) { + var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), + fillStyle = highlightColor, + barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = highlightColor; + + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); + } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + if (typeof c != "string") { + var co = $.color.parse(defaultColor); + if (c.brightness != null) + co = co.scale('rgb', c.brightness); + if (c.opacity != null) + co.a *= c.opacity; + c = co.toString(); + } + gradient.addColorStop(i / (l - 1), c); + } + + return gradient; + } + } + } + + $.plot = function(placeholder, data, options) { + //var t0 = new Date(); + var plot = new Plot($(placeholder), data, options, $.plot.plugins); + //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); + return plot; + }; + + $.plot.version = "0.8-alpha"; + + $.plot.plugins = []; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/common/lib/jquery.flot.pie.js b/common/lib/jquery.flot.pie.js new file mode 100644 index 00000000000..728d3cb7927 --- /dev/null +++ b/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 0) { + maxRadius *= shrink; + } + attempts += 1; + clear(); + if (options.series.pie.tilt <= 0.8) { + drawShadow(); + } + drawPie(); + } + + if (attempts >= redrawAttempts) { + clear(); + target.prepend("
    Could not draw pie with labels contained inside canvas
    "); + } + + // 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 = "" + text + ""; + 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;"; + $("
    ") + .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 "
    " + label + "
    " + Math.round(slice.percent) + "%
    "; + }, // 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); diff --git a/common/lib/jquery.flot.stack.js b/common/lib/jquery.flot.stack.js new file mode 100644 index 00000000000..79081b04919 --- /dev/null +++ b/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); diff --git a/common/lib/jquery.flot.time.js b/common/lib/jquery.flot.time.js new file mode 100644 index 00000000000..3af0f81f41d --- /dev/null +++ b/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 --git a/common/lib/json2.min.js b/common/lib/json2.min.js new file mode 100644 index 00000000000..08977b590c3 --- /dev/null +++ b/common/lib/json2.min.js @@ -0,0 +1 @@ +if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i',a,""].join(""),k.id=h,(l?k:m).innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(['#modernizr:after{content:"',l,'";visibility:hidden}'].join(""),function(b){a=b.offsetHeight>=1}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f= 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, '>') + .replace(/\r\n/g, '
    ') + .replace(/\r/g, '
    ') + .replace(/\n/g, '
    ') + .replace(/\t/g, '    ') + .replace(/ /g, '  ') + .replace(/<del>/g, '') + .replace(/<\/del>/g, ''); + 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 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 + "​" + char + trailer; + } + } + ); +} + +function htmlEntities(str) { + return String(str).replace( + /&/g, '&').replace( + //g, '>').replace( + /"/g, '"'); +} \ No newline at end of file diff --git a/common/lib/underscore.min.js b/common/lib/underscore.min.js new file mode 100644 index 00000000000..459f69104f9 --- /dev/null +++ b/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;e2;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;bd?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>1;d(a[g])=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=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/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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); \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 00000000000..33c8d068c81 --- /dev/null +++ b/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' + } +); \ No newline at end of file diff --git a/dashboards.js b/dashboards.js new file mode 100644 index 00000000000..c4517f2df80 --- /dev/null +++ b/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 : "*", + } + } + } + } +}; diff --git a/index.html b/index.html new file mode 100644 index 00000000000..53be5a83567 --- /dev/null +++ b/index.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + Kibana Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + + diff --git a/js/app.js b/js/app.js new file mode 100644 index 00000000000..175845a85e0 --- /dev/null +++ b/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' + }); + }]); diff --git a/js/controllers.js b/js/controllers.js new file mode 100644 index 00000000000..1cd25cf03ef --- /dev/null +++ b/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; + }); + } + +}); + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/directives.js b/js/directives.js new file mode 100644 index 00000000000..8fc0dea7488 --- /dev/null +++ b/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("
    ")(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); + }); + } + }; +}); + diff --git a/js/filters.js b/js/filters.js new file mode 100644 index 00000000000..c98e843b6c7 --- /dev/null +++ b/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); + } + }]); diff --git a/js/panels.js b/js/panels.js new file mode 100644 index 00000000000..883d89d31d3 --- /dev/null +++ b/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 '' + label + ' / ' + params.interval + + ''; + } + }, + 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
    %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 '
    '+ + label+'
    '+Math.round(series.percent)+'%
    '; + }, + 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 '
    '+ + label+'
    '+Math.round(series.percent)+'%
    '; + }, + 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 '' + label + ' / ' + params.interval + + ''; + } + }, + 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
    %m-%d", + label: "Datetime", + color: "#000", + }, + grid: { + backgroundColor: '#fff', + borderWidth: 0, + borderColor: '#eee', + color: "#eee", + hoverable: true, + } + }); + //elem.show(); + } + } + }; +}); diff --git a/js/services.js b/js/services.js new file mode 100644 index 00000000000..144aa3eb6ec --- /dev/null +++ b/js/services.js @@ -0,0 +1,5 @@ +/*jshint globalstrict:true */ +/*global angular:true */ +'use strict'; + +angular.module('kibana-dash.services', []); diff --git a/partials/dashboard.html b/partials/dashboard.html new file mode 100644 index 00000000000..6a8f93c7f63 --- /dev/null +++ b/partials/dashboard.html @@ -0,0 +1,14 @@ + +
    +
    +

    {{dashboards.title}} Last {{timespan}}

    +
    +
    +
    +
    +
    +

    {{panel_name}}

    +
    +
    +
    +
    \ No newline at end of file diff --git a/partials/search.html b/partials/search.html new file mode 100644 index 00000000000..9a7ab4873ac --- /dev/null +++ b/partials/search.html @@ -0,0 +1,8 @@ + +
    +
    + + +
    +
    \ No newline at end of file diff --git a/scripts/server.js b/scripts/server.js new file mode 100755 index 00000000000..3f74441e31b --- /dev/null +++ b/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('<', '<'). + replace('>', '>'). + replace('"', '"'); +} + +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('\n'); + res.write('Internal Server Error\n'); + res.write('

    Internal Server Error

    '); + res.write('
    ' + escapeHtml(util.inspect(error)) + '
    '); + 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('\n'); + res.write('404 Not Found\n'); + res.write('

    Not Found

    '); + res.write( + '

    The requested URL ' + + escapeHtml(path) + + ' was not found on this server.

    ' + ); + 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('\n'); + res.write('403 Forbidden\n'); + res.write('

    Forbidden

    '); + res.write( + '

    You do not have permission to access ' + + escapeHtml(path) + ' on this server.

    ' + ); + 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('\n'); + res.write('301 Moved Permanently\n'); + res.write('

    Moved Permanently

    '); + res.write( + '

    The document has moved here.

    ' + ); + 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('\n'); + res.write('' + escapeHtml(path) + '\n'); + res.write('\n'); + res.write('

    Directory: ' + escapeHtml(path) + '

    '); + res.write('
      '); + files.forEach(function(fileName) { + if (fileName.charAt(0) !== '.') { + res.write('
    1. ' + + escapeHtml(fileName) + '
    2. '); + } + }); + res.write('
    '); + res.end(); +}; + +// Must be last, +main(process.argv); diff --git a/sharable.json b/sharable.json new file mode 100644 index 00000000000..d36872113ef --- /dev/null +++ b/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" + } + } + } + } +}