http://git-wip-us.apache.org/repos/asf/karaf-decanter/blob/c4719b9d/elasticsearch-head/src/main/resources/app.js
----------------------------------------------------------------------
diff --git a/elasticsearch-head/src/main/resources/app.js 
b/elasticsearch-head/src/main/resources/app.js
new file mode 100644
index 0000000..008f0b0
--- /dev/null
+++ b/elasticsearch-head/src/main/resources/app.js
@@ -0,0 +1,4373 @@
+(function() {
+
+       var window = this,
+               $ = jQuery;
+
+       function ns( namespace ) {
+               return (namespace || "").split(".").reduce( function( space, 
name ) {
+                       return space[ name ] || ( space[ name ] = { ns: ns } );
+               }, this );
+       }
+
+       var app = ns("app");
+
+       var acx = ns("acx");
+
+       /**
+        * object iterator, returns an array with one element for each property 
of the object
+        * @function
+        */
+       acx.eachMap = function(obj, fn, thisp) {
+               var ret = [];
+               for(var n in obj) {
+                       ret.push(fn.call(thisp, n, obj[n], obj));
+               }
+               return ret;
+       };
+
+       /**
+        * augments the first argument with the properties of the second and 
subsequent arguments
+        * like {@link $.extend} except that existing properties are not 
overwritten
+        */
+       acx.augment = function() {
+               var args = Array.prototype.slice.call(arguments),
+                       src = (args.length === 1) ? this : args.shift(),
+                       augf = function(n, v) {
+                               if(! (n in src)) {
+                                       src[n] = v;
+                               }
+                       };
+               for(var i = 0; i < args.length; i++) {
+                       $.each(args[i], augf);
+               }
+               return src;
+       };
+
+       /**
+        * tests whether the argument is an array
+        * @function
+        */
+       acx.isArray = $.isArray;
+
+       /**
+        * tests whether the argument is an object
+        * @function
+        */
+       acx.isObject = function (value) {
+               return Object.prototype.toString.call(value) == "[object 
Object]";
+       };
+
+       /**
+        * tests whether the argument is a function
+        * @function
+        */
+       acx.isFunction = $.isFunction;
+
+       /**
+        * tests whether the argument is a date
+        * @function
+        */
+       acx.isDate = function (value) {
+               return Object.prototype.toString.call(value) == "[object Date]";
+       };
+
+       /**
+        * tests whether the argument is a regexp
+        * @function
+        */
+       acx.isRegExp = function (value) {
+               return Object.prototype.toString.call(value) == "[object 
RegExp]";
+       };
+
+       /**
+        * tests whether the value is blank or empty
+        * @function
+        */
+       acx.isEmpty = function (value, allowBlank) {
+               return value === null || value === undefined || 
((acx.isArray(value) && !value.length)) || (!allowBlank ? value === '' : false);
+       };
+
+       /**
+        * data type for performing chainable geometry calculations<br>
+        * can be initialised x,y | {x, y} | {left, top}
+        */
+       acx.vector = function(x, y) {
+               return new acx.vector.prototype.Init(x, y);
+       };
+
+       acx.vector.prototype = {
+               Init : function(x, y) {
+                       x = x || 0;
+                       this.y = isFinite(x.y) ? x.y : (isFinite(x.top) ? x.top 
: (isFinite(y) ? y : 0));
+                       this.x = isFinite(x.x) ? x.x : (isFinite(x.left) ? 
x.left : (isFinite(x) ? x : 0));
+               },
+               
+               add : function(i, j) {
+                       var d = acx.vector(i, j);
+                       return new this.Init(this.x + d.x, this.y + d.y);
+               },
+               
+               sub : function(i, j) {
+                       var d = acx.vector(i, j);
+                       return new this.Init(this.x - d.x, this.y - d.y);
+               },
+               
+               addX : function(i) {
+                       return new this.Init(this.x + i, this.y);
+               },
+               
+               addY : function(j) {
+                       return new this.Init(this.x, this.y + j);
+               },
+
+               mod : function(fn) { // runs a function against the x and y 
values
+                       return new this.Init({x: fn.call(this, this.x, "x"), y: 
fn.call(this, this.y, "y")});
+               },
+               
+               /** returns true if this is within a rectangle formed by the 
points p and q */
+               within : function(p, q) {
+                       return ( this.x >= ((p.x < q.x) ? p.x : q.x) && this.x 
<= ((p.x > q.x) ? p.x : q.x) &&
+                                       this.y >= ((p.y < q.y) ? p.y : q.y) && 
this.y <= ((p.y > q.y) ? p.y : q.y) );
+               },
+               
+               asOffset : function() {
+                       return { top: this.y, left: this.x };
+               },
+               
+               asSize : function() {
+                       return { height: this.y, width: this.x };
+               }
+       };
+
+       acx.vector.prototype.Init.prototype = acx.vector.prototype;
+
+       /**
+        * short cut functions for working with vectors and jquery.
+        * Each function returns the equivalent jquery value in a two 
dimentional vector
+        */
+       $.fn.vSize = function() { return acx.vector(this.width(), 
this.height()); };
+       $.fn.vOuterSize = function(margin) { return 
acx.vector(this.outerWidth(margin), this.outerHeight(margin)); };
+       $.fn.vScroll = function() { return acx.vector(this.scrollLeft(), 
this.scrollTop()); };
+       $.fn.vOffset = function() { return acx.vector(this.offset()); };
+       $.fn.vPosition = function() { return acx.vector(this.position()); };
+       $.Event.prototype.vMouse = function() { return acx.vector(this.pageX, 
this.pageY); };
+
+       /**
+        * object extensions (ecma5 compatible)
+        */
+       acx.augment(Object, {
+               keys: function(obj) {
+                       var ret = [];
+                       for(var n in obj) 
if(Object.prototype.hasOwnProperty.call(obj, n)) ret.push(n);
+                       return ret;
+               }
+       });
+
+       /**
+        * Array prototype extensions
+        */
+       acx.augment(Array.prototype, {
+               'contains' : function(needle) {
+                       return this.indexOf(needle) !== -1;
+               },
+
+               // returns a new array consisting of all the members that are 
in both arrays
+               'intersection' : function(b) {
+                       var ret = [];
+                       for(var i = 0; i < this.length; i++) {
+                               if(b.contains(this[i])) {
+                                       ret.push(this[i]);
+                               }
+                       }
+                       return ret;
+               },
+               
+               'remove' : function(value) {
+                       var i = this.indexOf(value);
+                       if(i !== -1) {
+                               this.splice(i, 1);
+                       }
+               }
+       });
+
+       /**
+        * String prototype extensions
+        */
+       acx.augment(String.prototype, {
+               'contains' : function(needle) {
+                       return this.indexOf(needle) !== -1;
+               },
+
+               'equalsIgnoreCase' : function(match) {
+                       return this.toLowerCase() === match.toLowerCase();
+               },
+
+               'escapeHtml' : function() {
+                       return this.replace(/&/g, '&amp;').replace(/</g, 
'&lt;').replace(/>/g, '&gt;');
+               },
+
+               'escapeJS' : function() {
+                       var meta = {'"':'\\"', '\\':'\\\\', '/':'\\/', 
'\b':'\\b', '\f':'\\f', '\n':'\\n', '\r':'\\r', '\t':'\\t'},
+                               xfrm = function(c) { return meta[c] || "\\u" + 
c.charCodeAt(0).toString(16).zeroPad(4); };
+                       return this.replace(new 
RegExp('(["\\\\\x00-\x1f\x7f-\uffff])', 'g'), xfrm);
+               },
+
+               'escapeRegExp' : function() {
+                       var ret = "", esc = "\\^$*+?.()=|{,}[]-";
+                       for ( var i = 0; i < this.length; i++) {
+                               ret += (esc.contains(this.charAt(i)) ? "\\" : 
"") + this.charAt(i);
+                       }
+                       return ret;
+               },
+               
+               'zeroPad' : function(len) {
+                       return ("0000000000" + this).substring(this.length - 
len + 10);
+               }
+       });
+
+       $.fn.forEach = Array.prototype.forEach;
+
+       // joey / jquery integration
+       $.joey = function( obj ) {
+               return $( window.joey( obj ) );
+       };
+
+       window.joey.plugins.push( function( obj ) {
+               if( obj instanceof jQuery ) {
+                       return obj[0];
+               }
+       });
+
+})();
+
+/**
+ * base class for creating inheritable classes
+ * based on resigs 'Simple Javascript Inheritance Class' (based on base2 and 
prototypejs)
+ * modified with static super and auto config
+ * @name Class
+ * @constructor
+ */
+(function( $, app ){
+
+       var ux = app.ns("ux");
+
+       var initializing = false, fnTest = /\b_super\b/;
+
+       ux.Class = function(){};
+
+       ux.Class.extend = function(prop) {
+               function Class() {
+                       if(!initializing) {
+                               var args = 
Array.prototype.slice.call(arguments);
+                               this.config = $.extend( function(t) { // 
automatically construct a config object based on defaults and last item passed 
into the constructor
+                                       return $.extend(t._proto && t._proto() 
&& arguments.callee(t._proto()) || {}, t.defaults);
+                               } (this) , args.pop() );
+                               this.init && this.init.apply(this, args); // 
automatically run the init function when class created
+                       }
+               }
+
+               initializing = true;
+               var prototype = new this();
+               initializing = false;
+               
+               var _super = this.prototype;
+               prototype._proto = function() {
+                       return _super;
+               };
+
+               for(var name in prop) {
+                       prototype[name] = typeof prop[name] === "function" && 
typeof _super[name] === "function" && fnTest.test(prop[name]) ?
+                               (function(name, fn){
+                                       return function() { this._super = 
_super[name]; return fn.apply(this, arguments); };
+                               })(name, prop[name]) : prop[name];
+               }
+
+               Class.prototype = prototype;
+               Class.constructor = Class;
+
+               Class.extend = arguments.callee; // make class extendable
+
+               return Class;
+       };
+})( this.jQuery, this.app );
+
+(function( app ) {
+
+       var ut = app.ns("ut");
+
+       ut.option_template = function(v) { return { tag: "OPTION", value: v, 
text: v }; };
+
+       ut.require_template = function(f) { return f.require ? { tag: "SPAN", 
cls: "require", text: "*" } : null; };
+
+
+       var sib_prefix = ['B','ki','Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
+
+       ut.byteSize_template = function(n) {
+               var i = 0;
+               while( n >= 1000 ) {
+                       i++;
+                       n /= 1024;
+               }
+               return (i === 0 ? n.toString() : n.toFixed( 3 - 
parseInt(n,10).toString().length )) + ( sib_prefix[ i ] || "..E" );
+       };
+
+       var sid_prefix = ['','k','M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
+
+       ut.count_template = function(n) {
+               var i = 0;
+               while( n >= 1000 ) {
+                       i++;
+                       n /= 1000;
+               }
+               return i === 0 ? n.toString() : ( n.toFixed( 3 - 
parseInt(n,10).toString().length ) + ( sid_prefix[ i ] || "..E" ) );
+       };
+
+})( this.app );
+
+(function( app ) {
+
+       var ux = app.ns("ux");
+
+       ux.Observable = ux.Class.extend((function() {
+               return {
+                       init: function() {
+                               this.observers = {};
+                               for( var opt in this.config ) { // 
automatically install observers that are defined in the configuration
+                                       if( opt.indexOf( 'on' ) === 0 ) {
+                                               this.on( opt.substring(2) , 
this.config[ opt ] );
+                                       }
+                               }
+                       },
+                       _getObs: function( type ) {
+                               return ( this.observers[ type.toLowerCase() ] 
|| ( this.observers[ type.toLowerCase() ] = [] ) );
+                       },
+                       on: function( type, fn, params, thisp ) {
+                               this._getObs( type ).push( { "cb" : fn, "args" 
: params || [] , "cx" : thisp || this } );
+                               return this;
+                       },
+                       fire: function( type ) {
+                               var params = Array.prototype.slice.call( 
arguments, 1 );
+                               this._getObs( type ).slice().forEach( function( 
ob ) {
+                                       ob["cb"].apply( ob["cx"], 
ob["args"].concat( params ) );
+                               } );
+                               return this;
+                       },
+                       removeAllObservers: function() {
+                               this.observers = {};
+                       },
+                       removeObserver: function( type, fn ) {
+                               var obs = this._getObs( type ),
+                                       index = obs.reduce( function(p, t, i) { 
return (t.cb === fn) ? i : p; }, -1 );
+                               if(index !== -1) {
+                                       obs.splice( index, 1 );
+                               }
+                               return this; // make observable functions 
chainable
+                       },
+                       hasObserver: function( type ) {
+                               return !! this._getObs( type ).length;
+                       }
+               };
+       })());
+
+})( this.app );
+(function( app ) {
+
+       var ux = app.ns("ux");
+
+       var extend = ux.Observable.extend;
+       var instance = function() {
+               if( ! ("me" in this) ) {
+                       this.me = new this();
+               }
+               return this.me;
+       };
+
+       ux.Singleton = ux.Observable.extend({});
+
+       ux.Singleton.extend = function() {
+               var Self = extend.apply( this, arguments );
+               Self.instance = instance;
+               return Self;
+       };
+
+})( this.app );
+
+(function( $, app ) {
+
+       var ux = app.ns("ux");
+
+       /**
+        * Provides drag and drop functionality<br>
+        * a DragDrop instance is created for each usage pattern and then used 
over and over again<br>
+        * first a dragObj is defined - this is the jquery node that will be 
dragged around<br>
+        * second, the event callbacks are defined - these allow you control 
the ui during dragging and run functions when successfully dropping<br>
+        * thirdly drop targets are defined - this is a list of DOM nodes, the 
constructor works in one of two modes:
+        * <li>without targets - objects can be picked up and dragged around, 
dragStart and dragStop events fire</li>
+        * <li>with targets - as objects are dragged over targets dragOver, 
dragOut and DragDrop events fire
+        * to start dragging call the DragDrop.pickup_handler() function, 
dragging stops when the mouse is released.
+        * @constructor
+        * The following options are supported
+        * <dt>targetSelector</dt>
+        *   <dd>an argument passed directly to jquery to create a list of 
targets, as such it can be a CSS style selector, or an array of DOM nodes<br>if 
target selector is null the DragDrop does Drag only and will not fire dragOver 
dragOut and dragDrop events</dd>
+        * <dt>pickupSelector</dt>
+        *   <dd>a jquery selector. The pickup_handler is automatically bound 
to matched elements (eg clicking on these elements starts the drag). if 
pickupSelector is null, the pickup_handler must be manually bound 
<code>$(el).bind("mousedown", dragdrop.pickup_handler)</code></dd>
+        * <dt>dragObj</dt>
+        *   <dd>the jQuery element to drag around when pickup is called. If 
not defined, dragObj must be set in onDragStart</dd>
+        * <dt>draggingClass</dt>
+        *   <dd>the class(es) added to items when they are being dragged</dd>
+        * The following observables are supported
+        * <dt>dragStart</dt>
+        *   <dd>a callback when start to 
drag<br><code>function(jEv)</code></dd>
+        * <dt>dragOver</dt>
+        *   <dd>a callback when we drag into a 
target<br><code>function(jEl)</code></dd>
+        * <dt>dragOut</dt>
+        *   <dd>a callback when we drag out of a target, or when we drop over 
a target<br><code>function(jEl)</code></dd>
+        * <dt>dragDrop</dt>
+        *   <dd>a callback when we drop on a 
target<br><code>function(jEl)</code></dd>
+        * <dt>dragStop</dt>
+        *   <dd>a callback when we stop 
dragging<br><code>function(jEv)</code></dd>
+        */
+       ux.DragDrop = ux.Observable.extend({
+               defaults : {
+                       targetsSelector : null,
+                       pickupSelector:   null,
+                       dragObj :         null,
+                       draggingClass :   "dragging"
+               },
+
+               init: function(options) {
+                       this._super(); // call the class initialiser
+               
+                       this.drag_handler = this.drag.bind(this);
+                       this.drop_handler = this.drop.bind(this);
+                       this.pickup_handler = this.pickup.bind(this);
+                       this.targets = [];
+                       this.dragObj = null;
+                       this.dragObjOffset = null;
+                       this.currentTarget = null;
+                       if(this.config.pickupSelector) {
+                               $(this.config.pickupSelector).bind("mousedown", 
this.pickup_handler);
+                       }
+               },
+
+               drag : function(jEv) {
+                       jEv.preventDefault();
+                       var mloc = acx.vector( this.lockX || jEv.pageX, 
this.lockY || jEv.pageY );
+                       
this.dragObj.css(mloc.add(this.dragObjOffset).asOffset());
+                       if(this.targets.length === 0) {
+                               return;
+                       }
+                       if(this.currentTarget !== null && 
mloc.within(this.currentTarget[1], this.currentTarget[2])) {
+                               return;
+                       }
+                       if(this.currentTarget !== null) {
+                               this.fire('dragOut', this.currentTarget[0]);
+                               this.currentTarget = null;
+                       }
+                       for(var i = 0; i < this.targets.length; i++) {
+                               if(mloc.within(this.targets[i][1], 
this.targets[i][2])) {
+                                       this.currentTarget = this.targets[i];
+                                       break;
+                               }
+                       }
+                       if(this.currentTarget !== null) {
+                               this.fire('dragOver', this.currentTarget[0]);
+                       }
+               },
+               
+               drop : function(jEv) {
+                       $(document).unbind("mousemove", this.drag_handler);
+                       $(document).unbind("mouseup", this.drop_handler);
+                       this.dragObj.removeClass(this.config.draggingClass);
+                       if(this.currentTarget !== null) {
+                               this.fire('dragOut', this.currentTarget[0]);
+                               this.fire('dragDrop', this.currentTarget[0]);
+                       }
+                       this.fire('dragStop', jEv);
+                       this.dragObj = null;
+               },
+               
+               pickup : function(jEv, opts) {
+                       $.extend(this.config, opts);
+                       this.fire('dragStart', jEv);
+                       this.dragObj = this.dragObj || this.config.dragObj;
+                       this.dragObjOffset = this.config.dragObjOffset || 
acx.vector(this.dragObj.offset()).sub(jEv.pageX, jEv.pageY);
+                       this.lockX = this.config.lockX ? jEv.pageX : 0;
+                       this.lockY = this.config.lockY ? jEv.pageY : 0;
+                       this.dragObj.addClass(this.config.draggingClass);
+                       if(!this.dragObj.get(0).parentNode || 
this.dragObj.get(0).parentNode.nodeType === 11) { // 11 = document fragment
+                               $(document.body).append(this.dragObj);
+                       }
+                       if(this.config.targetsSelector) {
+                               this.currentTarget = null;
+                               var targets = ( this.targets = [] );
+                               // create an array of elements optimised for 
rapid collision detection calculation
+                               $(this.config.targetsSelector).each(function(i, 
el) {
+                                       var jEl = $(el);
+                                       var tl = acx.vector(jEl.offset());
+                                       var br = tl.add(jEl.width(), 
jEl.height());
+                                       targets.push([jEl, tl, br]);
+                               });
+                       }
+                       $(document).bind("mousemove", this.drag_handler);
+                       $(document).bind("mouseup", this.drop_handler);
+                       this.drag_handler(jEv);
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( app ) {
+
+       var ux = app.ns("ux");
+
+       ux.FieldCollection = ux.Observable.extend({
+               defaults: {
+                       fields: []      // the collection of fields
+               },
+               init: function() {
+                       this._super();
+                       this.fields = this.config.fields;
+               },
+               validate: function() {
+                       return this.fields.reduce(function(r, field) {
+                               return r && field.validate();
+                       }, true);
+               },
+               getData: function(type) {
+                       return this.fields.reduce(function(r, field) {
+                               r[field.name] = field.val(); return r;
+                       }, {});
+               }
+       });
+
+})( this.app );
+
+(function( $, app ) {
+
+       var data = app.ns("data");
+       var ux = app.ns("ux");
+
+       data.Model = ux.Observable.extend({
+               defaults: {
+                       data: null
+               },
+               init: function() {
+                       this.set( this.config.data );
+               },
+               set: function( key, value ) {
+                       if( arguments.length === 1 ) {
+                               this._data = $.extend( {}, key );
+                       } else {
+                               key.split(".").reduce(function( ptr, prop, i, 
props) {
+                                       if(i === (props.length - 1) ) {
+                                               ptr[prop] = value;
+                                       } else {
+                                               if( !(prop in ptr) ) {
+                                                       ptr[ prop ] = {};
+                                               }
+                                               return ptr[prop];
+                                       }
+                               }, this._data );
+                       }
+               },
+               get: function( key ) {
+                       return key.split(".").reduce( function( ptr, prop ) {
+                               return ( ptr && ( prop in ptr ) ) ? ptr[ prop ] 
: undefined;
+                       }, this._data );
+               },
+       });
+})( this.jQuery, this.app );
+
+(function( app ) {
+
+       var data = app.ns("data");
+       var ux = app.ns("ux");
+
+       data.DataSourceInterface = ux.Observable.extend({
+               /*
+               properties
+                       meta = { total: 0 },
+                       headers = [ { name: "" } ],
+                       data = [ { column: value, column: value } ],
+                       sort = { column: "name", dir: "desc" }
+               events
+                       data: function( DataSourceInterface )
+                */
+               _getSummary: function(res) {
+                       this.summary = i18n.text("TableResults.Summary", 
res._shards.successful, res._shards.total, res.hits.total, (res.took / 
1000).toFixed(3));
+               },
+               _getMeta: function(res) {
+                       this.meta = { total: res.hits.total, shards: 
res._shards, tool: res.took };
+               }
+       });
+
+})( this.app );
+(function( app ) {
+
+       var data = app.ns("data");
+
+       data.ResultDataSourceInterface = data.DataSourceInterface.extend({
+               results: function(res) {
+                       this._getSummary(res);
+                       this._getMeta(res);
+                       this._getData(res);
+                       this.sort = {};
+                       this.fire("data", this);
+               },
+               _getData: function(res) {
+                       var columns = this.columns = [];
+                       this.data = res.hits.hits.map(function(hit) {
+                               var row = (function(path, spec, row) {
+                                       for(var prop in spec) {
+                                               if(acx.isObject(spec[prop])) {
+                                                       
arguments.callee(path.concat(prop), spec[prop], row);
+                                               } else 
if(acx.isArray(spec[prop])) {
+                                                       if(spec[prop].length) {
+                                                               
arguments.callee(path.concat(prop), spec[prop][0], row)
+                                                       }
+                                               } else {
+                                                       var dpath = 
path.concat(prop).join(".");
+                                                       if(! 
columns.contains(dpath)) {
+                                                               
columns.push(dpath);
+                                                       }
+                                                       row[dpath] = 
(spec[prop] || "null").toString();
+                                               }
+                                       }
+                                       return row;
+                               })([ hit._type ], hit, {});
+                               row._source = hit;
+                               return row;
+                       }, this);
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       /*
+       notes on elasticsearch terminology used in this project
+
+       indices[index] contains one or more
+       types[type] contains one or more
+       documents contain one or more
+       paths[path]
+       each path contains one element of data
+       each path maps to one field
+
+       eg PUT, "/twitter/tweet/1"
+       {
+               user: "mobz",
+               date: "2011-01-01",
+               message: "You know, for browsing elasticsearch",
+               name: {
+                       first: "Ben",
+                       last: "Birch"
+               }
+       }
+
+       creates
+               1 index: twitter
+                               this is the collection of index data
+               1 type: tweet
+                               this is the type of document (kind of like a 
table in sql)
+               1 document: /twitter/tweet/1
+                               this is an actual document in the index ( kind 
of like a row in sql)
+               5 paths: [ ["user"], ["date"], ["message"], ["name","first"], 
["name","last"] ]
+                               since documents can be heirarchical this maps a 
path from a document root to a piece of data
+               5 fields: [ "user", "date", "message", "first", "last" ]
+                               this is an indexed 'column' of data. fields are 
not heirarchical
+
+               the relationship between a path and a field is called a 
mapping. mappings also contain a wealth of information about how es indexes the 
field
+
+       notes
+       1) a path is stored as an array, the dpath is  <index> . <type> . 
path.join("."),
+                       which can be considered the canonical reference for a 
mapping
+       2) confusingly, es uses the term index for both the collection of 
indexed data, and the individually indexed fields
+                       so the term index_name is the same as field_name in 
this sense.
+
+       */
+
+       var data = app.ns("data");
+       var ux = app.ns("ux");
+
+       var coretype_map = {
+               "string" : "string",
+               "long" : "number",
+               "integer" : "number",
+               "float" : "number",
+               "double" : "number",
+               "ip" : "number",
+               "date" : "date",
+               "boolean" : "boolean",
+               "binary" : "binary",
+               "multi_field" : "multi_field"
+       };
+
+       var default_property_map = {
+               "string" : { "store" : "no", "index" : "analysed" },
+               "number" : { "store" : "no", "precision_steps" : 4 },
+               "date" : { "store" : "no", "format" : "dateOptionalTime", 
"index": "yes", "precision_steps": 4 },
+               "boolean" : { "store" : "no", "index": "yes" },
+               "binary" : { },
+               "multi_field" : { }
+       };
+
+       // parses metatdata from a cluster, into a bunch of useful data 
structures
+       data.MetaData = ux.Observable.extend({
+               defaults: {
+                       state: null // (required) response from a 
/_cluster/state request
+               },
+               init: function() {
+                       this._super();
+                       this.refresh(this.config.state);
+               },
+               getIndices: function(alias) {
+                       return alias ? this.aliases[alias] : this.indicesList;
+               },
+               // returns an array of strings containing all types that are in 
all of the indices passed in, or all types
+               getTypes: function(indices) {
+                       var indices = indices || [], types = [];
+                       this.typesList.forEach(function(type) {
+                               for(var i = 0; i < indices.length; i++) {
+                                       if(! 
this.indices[indices[i]].types.contains(type))
+                                               return;
+                               }
+                               types.push(type);
+                       }, this);
+                       return types;
+               },
+               refresh: function(state) {
+                       // currently metadata expects all like named fields to 
have the same type, even when from different types and indices
+                       var aliases = this.aliases = {};
+                       var indices = this.indices = {};
+                       var types = this.types = {};
+                       var fields = this.fields = {};
+                       var paths = this.paths = {};
+
+                       function createField( mapping, index, type, path, name 
) {
+                               var dpath = [ index, type ].concat( path 
).join( "." );
+                               var field_name = mapping.index_name || name;
+                               var field = paths[ dpath ] = fields[ field_name 
] || $.extend({
+                                       field_name : field_name,
+                                       core_type : coretype_map[ mapping.type 
],
+                                       dpaths : []
+                               }, default_property_map[ coretype_map[ 
mapping.type ] ], mapping );
+
+                               if (field.type === "multi_field" && typeof 
field.fields !== "undefined") {
+                                       for (var subField in field.fields) {
+                                               field.fields[ subField ] = 
createField( field.fields[ subField ], index, type, path.concat( subField ), 
name + "." + subField );
+                                       }
+                               }
+                               if (fields.dpaths) {
+                                       field.dpaths.push(dpath);
+                               }
+                               return field;
+                       }
+                       function getFields(properties, type, index, listeners) {
+                               (function procPath(prop, path) {
+                                       for (var n in prop) {
+                                               if ("properties" in prop[n]) {
+                                                       procPath( prop[ n 
].properties, path.concat( n ) );
+                                               } else {
+                                                       var field = 
createField(prop[n], index, type, path.concat(n), n);                           
                            
+                                                       listeners.forEach( 
function( listener ) {
+                                                               listener[ 
field.field_name ] = field;
+                                                       } );
+                                               }
+                                       }
+                               })(properties, []);
+                       }
+                       for (var index in state.metadata.indices) {
+                               indices[index] = {
+                                       types : [], fields : {}, paths : {}, 
parents : {}
+                               };
+                               indices[index].aliases = 
state.metadata.indices[index].aliases;
+                               indices[index].aliases.forEach(function(alias) {
+                                       (aliases[alias] || (aliases[alias] = 
[])).push(index);
+                               });
+                               var mapping = 
state.metadata.indices[index].mappings;
+                               for (var type in mapping) {
+                                       indices[index].types.push(type);
+                                       if ( type in types) {
+                                               types[type].indices.push(index);
+                                       } else {
+                                               types[type] = {
+                                                       indices : [index], 
fields : {}
+                                               };
+                                       }
+                                       getFields(mapping[type].properties, 
type, index, [fields, types[type].fields, indices[index].fields]);
+                                       if ( typeof mapping[type]._parent !== 
"undefined") {
+                                               indices[index].parents[type] = 
mapping[type]._parent.type;
+                                       }
+                               }
+                       }
+
+                       this.aliasesList = Object.keys(aliases);
+                       this.indicesList = Object.keys(indices);
+                       this.typesList = Object.keys(types);
+                       this.fieldsList = Object.keys(fields);
+               }
+       });
+
+})( this.app );        
+
+(function( app ) {
+
+       var data = app.ns("data");
+       var ux = app.ns("ux");
+
+       data.MetaDataFactory = ux.Observable.extend({
+               defaults: {
+                       cluster: null // (required) an app.services.Cluster
+               },
+               init: function() {
+                       this._super();
+                       this.config.cluster.get("_cluster/state", 
function(data) {
+                               this.metaData = new app.data.MetaData({state: 
data});
+                               this.fire("ready", this.metaData,  { 
originalData: data }); // TODO originalData needed for legacy ui.FilterBrowser
+                       }.bind(this));
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var data = app.ns("data");
+       var ux = app.ns("ux");
+
+       data.Query = ux.Observable.extend({
+               defaults: {
+                       cluster: null,  // (required) instanceof 
app.services.Cluster
+                       size: 50        // size of pages to return
+               },
+               init: function() {
+                       this._super();
+                       this.cluster = this.config.cluster;
+                       this.refuid = 0;
+                       this.refmap = {};
+                       this.indices = [];
+                       this.types = [];
+                       this.search = {
+                               fields : [ "_parent", "_source" ],
+                               query: { bool: { must: [], must_not: [], 
should: [] } },
+                               from: 0,
+                               size: this.config.size,
+                               sort: [],
+                               facets: {},
+                               version: true
+                       };
+                       this.defaultClause = this.addClause();
+                       this.history = [ this.getState() ];
+               },
+               clone: function() {
+                       var q = new data.Query({ cluster: this.cluster });
+                       q.restoreState(this.getState());
+                       for(var uqid in q.refmap) {
+                               q.removeClause(uqid);
+                       }
+                       return q;
+               },
+               getState: function() {
+                       return $.extend(true, {}, { search: this.search, 
indices: this.indices, types: this.types });
+               },
+               restoreState: function(state) {
+                       state = $.extend(true, {}, state || 
this.history[this.history.length - 1]);
+                       this.indices = state.indices;
+                       this.types = state.types;
+                       this.search = state.search;
+               },
+               getData: function() {
+                       return JSON.stringify(this.search);
+               },
+               query: function() {
+                       var state = this.getState();
+                       this.cluster.post(
+                                       (this.indices.join(",") || "_all") + 
"/" + ( this.types.length ? this.types.join(",") + "/" : "") + "_search",
+                                       this.getData(),
+                                       function(results) {
+                                               if(results === null) {
+                                                       
alert(i18n.text("Query.FailAndUndo"));
+                                                       this.restoreState();
+                                                       return;
+                                               }
+                                               this.history.push(state);
+
+                                               this.fire("results", this, 
results);
+                                       }.bind(this));
+               },
+               loadParents: function(res,metadata){
+                       //create data for mget
+                       var data = { docs :[] };
+                       var indexToTypeToParentIds = {};
+                       res.hits.hits.forEach(function(hit) {
+                       if (typeof hit.fields != "undefined"){
+                               if (typeof hit.fields._parent != "undefined"){
+                                       var parentType = 
metadata.indices[hit._index].parents[hit._type];
+                                       if (typeof 
indexToTypeToParentIds[hit._index] == "undefined"){
+                                               
indexToTypeToParentIds[hit._index] = new Object();
+                                       }
+                                       if (typeof 
indexToTypeToParentIds[hit._index][hit._type] == "undefined"){
+                                               
indexToTypeToParentIds[hit._index][hit._type] = new Object();
+                                       }
+                                       if (typeof 
indexToTypeToParentIds[hit._index][hit._type][hit.fields._parent] == 
"undefined"){
+                                               
indexToTypeToParentIds[hit._index][hit._type][hit.fields._parent] = null;
+                                               data.docs.push({ 
_index:hit._index, _type:parentType, _id:hit.fields._parent});
+                                       }
+                               }
+                       }
+               });
+
+               //load parents
+               var state = this.getState();
+                       this.cluster.post("_mget",JSON.stringify(data),
+                               function(results) {
+                                       if(results === null) {
+                                               
alert(i18n.text("Query.FailAndUndo"));
+                                               this.restoreState();
+                                               return;
+                                       }
+                                       this.history.push(state);
+                                       var indexToTypeToParentIdToHit = new 
Object();
+                                       results.docs.forEach(function(doc) {
+                                               if (typeof 
indexToTypeToParentIdToHit[doc._index] == "undefined"){
+                                               
indexToTypeToParentIdToHit[doc._index] = new Object();
+                                       }
+                                       
+                                       if (typeof 
indexToTypeToParentIdToHit[doc._index][doc._type] == "undefined"){
+                                               
indexToTypeToParentIdToHit[doc._index][doc._type] = new Object();
+                                       }
+                                       
+                                       
indexToTypeToParentIdToHit[doc._index][doc._type][doc._id] = doc;
+                                       });
+                                       
+                                       res.hits.hits.forEach(function(hit) {
+                                               if (typeof hit.fields != 
"undefined"){
+                                                       if (typeof 
hit.fields._parent != "undefined"){
+                                                               var parentType 
= metadata.indices[hit._index].parents[hit._type];
+                                                               hit._parent = 
indexToTypeToParentIdToHit[hit._index][parentType][hit.fields._parent];
+                                                       }
+                                               }
+                                       });
+
+                                       this.fire("resultsWithParents", this, 
res);
+                               }.bind(this));
+               },
+               setPage: function(page) {
+                       this.search.from = this.config.size * (page - 1);
+               },
+               setSort: function(index, desc) {
+                       var sortd = {}; sortd[index] = { reverse: !!desc };
+                       this.search.sort.unshift( sortd );
+                       for(var i = 1; i < this.search.sort.length; i++) {
+                               if(Object.keys(this.search.sort[i])[0] === 
index) {
+                                       this.search.sort.splice(i, 1);
+                                       break;
+                               }
+                       }
+               },
+               setIndex: function(index, add) {
+                       if(add) {
+                               if(! this.indices.contains(index)) 
this.indices.push(index);
+                       } else {
+                               this.indices.remove(index);
+                       }
+                       this.fire("setIndex", this, { index: index, add: !!add 
});
+               },
+               setType: function(type, add) {
+                       if(add) {
+                               if(! this.types.contains(type)) 
this.types.push(type);
+                       } else {
+                               this.types.remove(type);
+                       }
+                       this.fire("setType", this, { type: type, add: !!add });
+               },
+               addClause: function(value, field, op, bool) {
+                       bool = bool || "should";
+                       op = op || "match_all";
+                       field = field || "_all";
+                       var clause = this._setClause(value, field, op, bool);
+                       var uqid = "q-" + this.refuid++;
+                       this.refmap[uqid] = { clause: clause, value: value, 
field: field, op: op, bool: bool };
+                       if(this.search.query.bool.must.length + 
this.search.query.bool.should.length > 1) {
+                               this.removeClause(this.defaultClause);
+                       }
+                       this.fire("queryChanged", this, { uqid: uqid, search: 
this.search} );
+                       return uqid; // returns reference to inner query object 
to allow fast updating
+               },
+               removeClause: function(uqid) {
+                       var ref = this.refmap[uqid],
+                               bool = this.search.query.bool[ref.bool];
+                       bool.remove(ref.clause);
+                       if(this.search.query.bool.must.length + 
this.search.query.bool.should.length === 0) {
+                               this.defaultClause = this.addClause();
+                       }
+               },
+               addFacet: function(facet) {
+                       var facetId = "f-" + this.refuid++;
+                       this.search.facets[facetId] = facet;
+                       this.refmap[facetId] = { facetId: facetId, facet: facet 
};
+                       return facetId;
+               },
+               removeFacet: function(facetId) {
+                       delete this.search.facets[facetId];
+                       delete this.refmap[facetId];
+               },
+               _setClause: function(value, field, op, bool) {
+                       var clause = {}, query = {};
+                       if(op === "match_all") {
+                       } else if(op === "query_string") {
+                               query["default_field"] = field;
+                               query["query"] = value;
+                       } else if(op === "missing") {
+                               op = "constant_score"
+                               var missing = {}, filter = {};
+                               missing["field"] = field;
+                               filter["missing"] = missing
+                               query["filter"] = filter;
+                       } else {
+                               query[field] = value;
+                       }
+                       clause[op] = query;
+                       this.search.query.bool[bool].push(clause);
+                       return clause;
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var data = app.ns("data");
+
+       data.QueryDataSourceInterface = data.DataSourceInterface.extend({
+               defaults: {
+                       metadata: null, // (required) instanceof 
app.data.MetaData, the cluster metadata
+                       query: null     // (required) instanceof app.data.Query 
the data source
+               },
+               init: function() {
+                       this._super();
+                       this.config.query.on("results", 
this._results_handler.bind(this) );
+                       this.config.query.on("resultsWithParents", 
this._load_parents.bind(this) );
+               },
+               _results_handler: function(query, res) {
+                       this._getSummary(res);
+                       this._getMeta(res);
+                       var sort = query.search.sort[0] || { "_score": { 
reverse: false }};
+                       var sortField = Object.keys(sort)[0];
+                       this.sort = { column: sortField, dir: 
(sort[sortField].reverse ? "asc" : "desc") };
+                       this._getData(res, this.config.metadata);
+                       this.fire("data", this);
+               },
+               _load_parents: function(query, res) {
+                       query.loadParents(res, this.config.metadata);
+               },
+               _getData: function(res, metadata) {
+                       var metaColumns = ["_index", "_type", "_id", "_score"];
+                       var columns = this.columns = [].concat(metaColumns);
+
+                       this.data = res.hits.hits.map(function(hit) {
+                               var row = (function(path, spec, row) {
+                                       for(var prop in spec) {
+                                               if(acx.isObject(spec[prop])) {
+                                                       
arguments.callee(path.concat(prop), spec[prop], row);
+                                               } else 
if(acx.isArray(spec[prop])) {
+                                                       if(spec[prop].length) {
+                                                               
arguments.callee(path.concat(prop), spec[prop][0], row)
+                                                       }
+                                               } else {
+                                                       var dpath = 
path.concat(prop).join(".");
+                                                       
if(metadata.paths[dpath]) {
+                                                               var field_name 
= metadata.paths[dpath].field_name;
+                                                               if(! 
columns.contains(field_name)) {
+                                                                       
columns.push(field_name);
+                                                               }
+                                                               row[field_name] 
= (spec[prop] === null ? "null" : spec[prop] ).toString();
+                                                       } else {
+                                                               // TODO: field 
not in metadata index
+                                                       }
+                                               }
+                                       }
+                                       return row;
+                               })([ hit._index, hit._type ], hit._source, {});
+                               metaColumns.forEach(function(n) { row[n] = 
hit[n]; });
+                               row._source = hit;
+                               if (typeof hit._parent!= "undefined") {
+                                       (function(prefix, path, spec, row) {
+                                       for(var prop in spec) {
+                                               if(acx.isObject(spec[prop])) {
+                                                       
arguments.callee(prefix, path.concat(prop), spec[prop], row);
+                                               } else 
if(acx.isArray(spec[prop])) {
+                                                       if(spec[prop].length) {
+                                                               
arguments.callee(prefix, path.concat(prop), spec[prop][0], row)
+                                                       }
+                                               } else {
+                                                       var dpath = 
path.concat(prop).join(".");
+                                                       
if(metadata.paths[dpath]) {
+                                                               var field_name 
= metadata.paths[dpath].field_name;
+                                                               var column_name 
= prefix+"."+field_name;
+                                                               if(! 
columns.contains(column_name)) {
+                                                                       
columns.push(column_name);
+                                                               }
+                                                               
row[column_name] = (spec[prop] === null ? "null" : spec[prop] ).toString();
+                                                       } else {
+                                                               // TODO: field 
not in metadata index
+                                                       }
+                                               }
+                                       }
+                                       
})(hit._parent._type,[hit._parent._index, hit._parent._type], 
hit._parent._source, row);
+                               }
+                               return row;
+                       }, this);
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var data = app.ns("data");
+       var ux = app.ns("ux");
+
+       data.BoolQuery = ux.Observable.extend({
+               defaults: {
+                       size: 50                // size of pages to return
+               },
+               init: function() {
+                       this._super();
+                       this.refuid = 0;
+                       this.refmap = {};
+                       this.search = {
+                               query: { bool: { must: [], must_not: [], 
should: [] } },
+                               from: 0,
+                               size: this.config.size,
+                               sort: [],
+                               facets: {}
+                       };
+                       this.defaultClause = this.addClause();
+               },
+               setSize: function(size) {
+                       this.search.size = parseInt( size, 10 );
+               },
+               setPage: function(page) {
+                       this.search.from = this.config.size * (page - 1) + 1;
+               },
+               addClause: function(value, field, op, bool) {
+                       bool = bool || "should";
+                       op = op || "match_all";
+                       field = field || "_all";
+                       var clause = this._setClause(value, field, op, bool);
+                       var uqid = "q-" + this.refuid++;
+                       this.refmap[uqid] = { clause: clause, value: value, 
field: field, op: op, bool: bool };
+                       if(this.search.query.bool.must.length + 
this.search.query.bool.should.length > 1) {
+                               this.removeClause(this.defaultClause);
+                       }
+                       this.fire("queryChanged", this, { uqid: uqid, search: 
this.search} );
+                       return uqid; // returns reference to inner query object 
to allow fast updating
+               },
+               removeClause: function(uqid) {
+                       var ref = this.refmap[uqid],
+                               bool = this.search.query.bool[ref.bool];
+                       var clauseIdx = bool.indexOf(ref.clause);
+                       // Check that this clause hasn't already been removed
+                       if (clauseIdx >=0) {
+                               bool.splice(clauseIdx, 1);
+                       }
+               },
+               _setClause: function(value, field, op, bool) {
+                       var clause = {}, query = {};
+                       if(op === "match_all") {
+                       } else if(op === "query_string") {
+                               query["default_field"] = field;
+                               query["query"] = value;
+                       } else if(op === "missing") {
+                               op = "constant_score"
+                               var missing = {}, filter = {};
+                               missing["field"] = field;
+                               filter["missing"] = missing
+                               query["filter"] = filter;
+                       } else {
+                               query[field] = value;
+                       }
+                       clause[op] = query;
+                       this.search.query.bool[bool].push(clause);
+                       return clause;
+               },
+               getData: function() {
+                       return JSON.stringify(this.search);
+               }
+       });
+
+})( this.app );
+(function( app ) {
+       
+       var ux = app.ns("ux");
+       var services = app.ns("services");
+
+       services.Preferences = ux.Singleton.extend({
+               init: function() {
+                       this._storage = window.localStorage;
+                       this._setItem("__version", 1 );
+               },
+               get: function( key ) {
+                       return this._getItem( key );
+               },
+               set: function( key, val ) {
+                       return this._setItem( key, val );
+               },
+               _getItem: function( key ) {
+                       try {
+                               return JSON.parse( this._storage.getItem( key ) 
);
+                       } catch(e) {
+                               console.warn( e );
+                               return undefined;
+                       }
+               },
+               _setItem: function( key, val ) {
+                       try {
+                               return this._storage.setItem( key, 
JSON.stringify( val ) );
+                       } catch(e) {
+                               console.warn( e );
+                               return undefined;
+                       }
+               }
+       });
+
+})( this.app );
+
+(function( $, app ) {
+
+       var services = app.ns("services");
+       var ux = app.ns("ux");
+
+       function parse_version( v ) {
+               return v.match(/^(\d+)\.(\d+)\.(\d+)/).slice(1,4).map( 
function(d) { return parseInt(d || 0, 10); } );
+       }
+
+       services.Cluster = ux.Class.extend({
+               defaults: {
+                       base_uri: null
+               },
+               init: function() {
+                       this.base_uri = this.config.base_uri;
+               },
+               setVersion: function( v ) {
+                       this.version = v;
+                       this._version_parts = parse_version( v );
+               },
+               versionAtLeast: function( v ) {
+                       var testVersion = parse_version( v );
+                       for( var i = 0; i < 3; i++ ) {
+                               if( testVersion[i] !== this._version_parts[i] ) 
{
+                                       return testVersion[i] < 
this._version_parts[i];
+                               }
+                       }
+                       return true;
+               },
+               request: function( params ) {
+                       return $.ajax( $.extend({
+                               url: this.base_uri + params.path,
+                               dataType: "json",
+                               error: function(xhr, type, message) {
+                                       if("console" in window) {
+                                               console.log({ "XHR Error": 
type, "message": message });
+                                       }
+                               }
+                       },  params) );
+               },
+               "get": function(path, success) { return this.request( { type: 
"GET", path: path, success: success } ); },
+               "post": function(path, data, success) { return this.request( { 
type: "POST", path: path, data: data, success: success } ); },
+               "put": function(path, data, success) { return this.request( { 
type: "PUT", path: path, data: data, success: success } ); },
+               "delete": function(path, data, success) { return this.request( 
{ type: "DELETE", path: path, data: data, success: success } ); }
+       });
+
+})( this.jQuery, this.app );
+
+(function( app ) {
+
+       var services = app.ns("services");
+       var ux = app.ns("ux");
+
+       services.ClusterState = ux.Observable.extend({
+               defaults: {
+                       cluster: null
+               },
+               init: function() {
+                       this._super();
+                       this.cluster = this.config.cluster;
+                       this.clusterState = null;
+                       this.status = null;
+                       this.nodeStats = null;
+                       this.clusterNodes = null;
+               },
+               refresh: function() {
+                       var self = this, clusterState, status, nodeStats, 
clusterNodes, clusterHealth;
+                       function updateModel() {
+                               if( clusterState && status && nodeStats && 
clusterNodes && clusterHealth ) {
+                                       this.clusterState = clusterState;
+                                       this.status = status;
+                                       this.nodeStats = nodeStats;
+                                       this.clusterNodes = clusterNodes;
+                                       this.clusterHealth = clusterHealth;
+                                       this.fire( "data", this );
+                               }
+                       }
+                       this.cluster.get("_cluster/state", function( data ) {
+                               clusterState = data;
+                               updateModel.call( self );
+                       });
+                       this.cluster.get("_status", function( data ) {
+                               status = data;
+                               updateModel.call( self );
+                       });
+                       this.cluster.get("_nodes/stats?all=true", function( 
data ) {
+                               nodeStats = data;
+                               updateModel.call( self );
+                       });
+                       this.cluster.get("_nodes", function( data ) {
+                               clusterNodes = data;
+                               updateModel.call( self );
+                       });
+                       this.cluster.get("_cluster/health", function( data ) {
+                               clusterHealth = data;
+                               updateModel.call( self );
+                       });
+               },
+               _clusterState_handler: function(state) {
+                       this.clusterState = state;
+                       this.redraw("clusterState");
+               },
+               _status_handler: function(status) {
+                       this.status = status;
+                       this.redraw("status");
+               },
+               _clusterNodeStats_handler: function(stats) {
+                       this.nodeStats = stats;
+                       this.redraw("nodeStats");
+               },
+               _clusterNodes_handler: function(nodes) {
+                       this.clusterNodes = nodes;
+                       this.redraw("clusterNodes");
+               },
+               _clusterHealth_handler: function(health) {
+                       this.clusterHealth = health;
+                       this.redraw("status");
+               }
+       });
+
+})( this.app );
+(function( $, joey, app ) {
+
+       var ui = app.ns("ui");
+       var ux = app.ns("ux");
+
+       ui.AbstractWidget = ux.Observable.extend({
+               defaults : {
+                       id: null     // the id of the widget
+               },
+
+               el: null,       // this is the jquery wrapped dom element(s) 
that is the root of the widget
+
+               init: function() {
+                       this._super();
+                       for(var prop in this) {       // automatically bind all 
the event handlers
+                               if(prop.contains("_handler")) {
+                                       this[prop] = this[prop].bind(this);
+                               }
+                       }
+               },
+
+               id: function(suffix) {
+                       return this.config.id ? (this.config.id + (suffix ? "-" 
+ suffix : "")) : undefined;
+               },
+
+               attach: function( parent, method ) {
+                       if( parent ) {
+                               this.el[ method || "appendTo"]( parent );
+                       }
+                       this.fire("attached", this );
+                       return this;
+               },
+
+               remove: function() {
+                       this.el.remove();
+                       this.fire("removed", this );
+                       this.removeAllObservers();
+                       this.el = null;
+                       return this;
+               }
+       });
+
+       joey.plugins.push( function( obj ) {
+               if( obj instanceof ui.AbstractWidget ) {
+                       return obj.el[0];
+               }
+       });
+
+})( this.jQuery, this.joey, this.app );
+
+(function( $, app, joey ) {
+
+       var ui = app.ns("ui");
+
+       ui.AbstractField = ui.AbstractWidget.extend({
+
+               defaults: {
+                       name : "",                      // (required) - name of 
the field
+                       require: false, // validation requirements (false, 
true, regexp, function)
+                       value: "",                      // default value
+                       label: ""                               // human 
readable label of this field
+               },
+
+               init: function(parent) {
+                       this._super();
+                       this.el = $.joey(this._main_template());
+                       this.field = 
this.el.find("[name="+this.config.name+"]");
+                       this.label = this.config.label;
+                       this.require = this.config.require;
+                       this.name = this.config.name;
+                       this.val( this.config.value );
+                       this.attach( parent );
+               },
+
+               val: function( val ) {
+                       if(val === undefined) {
+                               return this.field.val();
+                       } else {
+                               this.field.val( val );
+                               return this;
+                       }
+               },
+
+               validate: function() {
+                       var val = this.val(), req = this.require;
+                       if( req === false ) {
+                               return true;
+                       } else if( req === true ) {
+                               return val.length > 0;
+                       } else if( req.test && $.isFunction(req.test) ) {
+                               return req.test( val );
+                       } else if( $.isFunction(req) ) {
+                               return req( val, this );
+                       }
+               }
+
+       });
+
+})( this.jQuery, this.app, this.joey );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.TextField = ui.AbstractField.extend({
+               init: function() {
+                       this._super();
+               },
+               _keyup_handler: function() {
+                       this.fire("change", this );
+               },
+               _main_template: function() {
+                       return { tag: "DIV", id: this.id(), cls: "uiField 
uiTextField", children: [
+                               { tag: "INPUT",
+                                       type: "text",
+                                       name: this.config.name,
+                                       placeholder: this.config.placeholder,
+                                       onkeyup: this._keyup_handler
+                               }
+                       ]};
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.CheckField = ui.AbstractField.extend({
+               _main_template: function() { return (
+                       { tag: "DIV", id: this.id(), cls: "uiCheckField", 
children: [
+                               { tag: "INPUT", type: "checkbox", name: 
this.config.name, checked: !!this.config.value }
+                       ] }
+               ); },
+               validate: function() {
+                       return this.val() || ( ! this.require );
+               },
+               val: function( val ) {
+                       if( val === undefined ) {
+                               return !!this.field.attr( "checked" );
+                       } else {
+                               this.field.attr( "checked", !!val );
+                       }
+               }
+       });
+
+})( this.app );
+
+
+
+(function( $, joey, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.Button = ui.AbstractWidget.extend({
+               defaults : {
+                       label: "",                 // the label text
+                       disabled: false,           // create a disabled button
+                       autoDisable: false         // automatically disable the 
button when clicked
+               },
+
+               _baseCls: "uiButton",
+
+               init: function(parent) {
+                       this._super();
+                       this.el = $.joey(this.button_template())
+                               .bind("click", this.click_handler);
+                       this.config.disabled && this.disable();
+                       this.attach( parent );
+               },
+
+               click_handler: function(jEv) {
+                       if(! this.disabled) {
+                               this.fire("click", jEv, this);
+                               this.config.autoDisable && this.disable();
+                       }
+               },
+
+               enable: function() {
+                       this.el.removeClass("disabled");
+                       this.disabled = false;
+                       return this;
+               },
+
+               disable: function(disable) {
+                       if(disable === false) {
+                                       return this.enable();
+                       }
+                       this.el.addClass("disabled");
+                       this.disabled = true;
+                       return this;
+               },
+
+               button_template: function() { return (
+                       { tag: 'BUTTON', type: 'button', id: this.id(), cls: 
this._baseCls, children: [
+                               { tag: 'DIV', cls: 'uiButton-content', 
children: [
+                                       { tag: 'DIV', cls: 'uiButton-label', 
text: this.config.label }
+                               ] }
+                       ] }
+               ); }
+       });
+
+})( this.jQuery, this.joey, this.app );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.MenuButton = app.ui.Button.extend({
+               defaults: {
+                       menu: null
+               },
+               _baseCls: "uiButton uiMenuButton",
+               init: function(parent) {
+                       this._super(parent);
+                       this.menu = this.config.menu;
+                       this.on("click", this.openMenu_handler);
+                       this.menu.on("open", function() { 
this.el.addClass("active"); }.bind(this));
+                       this.menu.on("close", function() { 
this.el.removeClass("active"); }.bind(this));
+               },
+               openMenu_handler: function(jEv) {
+                       this.menu && this.menu.open(jEv);
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.SplitButton = ui.AbstractWidget.extend({
+               defaults: {
+                       items: [],
+                       label: ""
+               },
+               _baseCls: "uiSplitButton",
+               init: function( parent ) {
+                       this._super( parent );
+                       this.value = null;
+                       this.button = new ui.Button({
+                               label: this.config.label,
+                               onclick: this._click_handler
+                       });
+                       this.menu = new ui.SelectMenuPanel({
+                               value: this.config.value,
+                               items: this._getItems(),
+                               onSelect: this._select_handler
+                       });
+                       this.menuButton = new ui.MenuButton({
+                               label: "\u00a0",
+                               menu: this.menu
+                       });
+                       this.el = $.joey(this._main_template());
+               },
+               remove: function() {
+                       this.menu.remove();
+               },
+               disable: function() {
+                       this.button.disable();
+               },
+               enable: function() {
+                       this.button.enable();
+               },
+               _click_handler: function() {
+                       this.fire("click", this, { value: this.value } );
+               },
+               _select_handler: function( panel, event ) {
+                       this.fire( "select", this, event );
+               },
+               _getItems: function() {
+                       return this.config.items;
+               },
+               _main_template: function() {
+                       return { tag: "DIV", cls: this._baseCls, children: [
+                               this.button, this.menuButton
+                       ] };
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( $, app, i18n ) {
+
+       var ui = app.ns("ui");
+
+       ui.RefreshButton = ui.SplitButton.extend({
+               defaults: {
+                       timer: -1
+               },
+               init: function( parent ) {
+                       this.config.label = i18n.text("General.RefreshResults");
+                       this._super( parent );
+                       this.set( this.config.timer );
+               },
+               set: function( value ) {
+                       this.value = value;
+                       window.clearInterval( this._timer );
+                       if( this.value > 0 ) {
+                               this._timer = window.setInterval( 
this._refresh_handler, this.value );
+                       }
+               },
+               _click_handler: function() {
+                       this._refresh_handler();
+               },
+               _select_handler: function( el, event ) {
+                       this.set( event.value );
+                       this.fire("change", this );
+               },
+               _refresh_handler: function() {
+                       this.fire("refresh", this );
+               },
+               _getItems: function() {
+                       return [
+                               { text: i18n.text("General.ManualRefresh"), 
value: -1 },
+                               { text: i18n.text("General.RefreshQuickly"), 
value: 100 },
+                               { text: i18n.text("General.Refresh5seconds"), 
value: 5000 },
+                               { text: i18n.text("General.Refresh1minute"), 
value: 60000 }
+                       ];
+               }
+       });
+
+})( this.jQuery, this.app, this.i18n );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.Toolbar = ui.AbstractWidget.extend({
+               defaults: {
+                       label: "",
+                       left: [],
+                       right: []
+               },
+               init: function(parent) {
+                       this._super();
+                       this.el = $.joey(this._main_template());
+               },
+               _main_template: function() {
+                       return { tag: "DIV", cls: "uiToolbar", children: [
+                               { tag: "DIV", cls: "pull-left", children: [
+                                       { tag: "H2", text: this.config.label }
+                               ].concat(this.config.left) },
+                               { tag: "DIV", cls: "pull-right", children: 
this.config.right }
+                       ]};
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.AbstractPanel = ui.AbstractWidget.extend({
+               defaults: {
+                       body: null,            // initial content of the body
+                       modal: true,           // create a modal panel - 
creates a div that blocks interaction with page
+                       height: 'auto',        // panel height
+                       width: 400,            // panel width (in pixels)
+                       open: false,           // show the panel when it is 
created
+                       parent: 'BODY',        // node that panel is attached to
+                       autoRemove: false      // remove the panel from the dom 
and destroy it when the widget is closed
+               },
+               shared: {  // shared data for all instances of ui.Panel and 
decendants
+                       stack: [], // array of all open panels
+                       modal: $( { tag: "DIV", id: "uiModal", css: { opacity: 
0.2, position: "absolute", top: "0px", left: "0px" } } )
+               },
+               init: function() {
+                       this._super();
+               },
+               open: function( ev ) {
+                       this.el
+                               .css( { visibility: "hidden" } )
+                               .appendTo( this.config.parent )
+                               .css( this._getPosition( ev ) )
+                               .css( { zIndex: (this.shared.stack.length ? 
(+this.shared.stack[this.shared.stack.length - 1].el.css("zIndex") + 10) : 100) 
} )
+                               .css( { visibility: "visible", display: "block" 
} );
+                       this.shared.stack.remove(this);
+                       this.shared.stack.push(this);
+                       this._setModal();
+                       $(document).bind("keyup", this._close_handler );
+                       this.fire("open", { source: this, event: ev } );
+                       return this;
+               },
+               close: function() {
+                       var index = this.shared.stack.indexOf(this);
+                       if(index !== -1) {
+                               this.shared.stack.splice(index, 1);
+                               this.el.css( { left: "-2999px" } ); // move the 
dialog to the left rather than hiding to prevent ie6 rendering artifacts
+                               this._setModal();
+                               this.fire("close", this );
+                               if(this.config.autoRemove) {
+                                       this.remove();
+                               }
+                       }
+                       return this;
+               },
+               // close the panel and remove it from the dom, destroying it 
(you can not reuse the panel after calling remove)
+               remove: function() {
+                       this.close();
+                       $(document).unbind("keyup", this._close_handler );
+                       this._super();
+               },
+               // starting at the top of the stack, find the first panel that 
wants a modal and put it just underneath, otherwise remove the modal
+               _setModal: function() {
+                       for(var stackPtr = this.shared.stack.length - 1; 
stackPtr >= 0; stackPtr--) {
+                               if(this.shared.stack[stackPtr].config.modal) {
+                                       this.shared.modal
+                                               .appendTo( document.body )
+                                               .css( { zIndex: 
this.shared.stack[stackPtr].el.css("zIndex") - 5 } )
+                                               .css( 
$(document).vSize().asSize() );
+                                       return;
+                               }
+                       }
+                       this.shared.modal.remove(); // no panels that want a 
modal were found
+               },
+               _getPosition: function() {
+                       return $(window).vSize()                        // get 
the current viewport size
+                               .sub(this.el.vSize())                         
// subtract the size of the panel
+                               .mod(function(s) { return s / 2; })           
// divide by 2 (to center it)
+                               .add($(document).vScroll())                   
// add the current scroll offset
+                               .mod(function(s) { return Math.max(5, s); })  
// make sure the panel is not off the edge of the window
+                               .asOffset();                                  
// and return it as a {top, left} object
+               },
+               _close_handler: function( ev ) {
+                       if( ev.type === "keyup" && ev.keyCode !== 27) { return; 
} // press esc key to close
+                       $(document).unbind("keyup", this._close_handler);
+                       this.close( ev );
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.DraggablePanel = ui.AbstractPanel.extend({
+               defaults: {
+       //              title: ""   // (required) text for the panel title
+               },
+
+               _baseCls: "uiPanel",
+
+               init: function() {
+                       this._super();
+                       this.body = $(this._body_template());
+                       this.title = $(this._title_template());
+                       this.el = $.joey( this._main_template() );
+                       this.el.css( { width: this.config.width } );
+                       this.dd = new app.ux.DragDrop({
+                               pickupSelector: 
this.el.find(".uiPanel-titleBar"),
+                               dragObj: this.el
+                       });
+                       // open the panel if set in configuration
+                       this.config.open && this.open();
+               },
+
+               setBody: function(body) {
+                               this.body.empty().append(body);
+               },
+               _body_template: function() { return { tag: "DIV", cls: 
"uiPanel-body", css: { height: this.config.height + (this.config.height === 
'auto' ? "" : "px" ) }, children: [ this.config.body ] }; },
+               _title_template: function() { return { tag: "SPAN", cls: 
"uiPanel-title", text: this.config.title }; },
+               _main_template: function() { return (
+                       { tag: "DIV", id: this.id(), cls: this._baseCls, 
children: [
+                               { tag: "DIV", cls: "uiPanel-titleBar", 
children: [
+                                       { tag: "DIV", cls: "uiPanel-close", 
onclick: this._close_handler, text: "x" },
+                                       this.title
+                               ]},
+                               this.body
+                       ] }
+               ); }
+       });
+
+})( this.jQuery, this.app );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.InfoPanel = ui.DraggablePanel.extend({
+               _baseCls: "uiPanel uiInfoPanel"
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.DialogPanel = ui.DraggablePanel.extend({
+               _commit_handler: function(jEv) {
+                       this.fire("commit", this, { jEv: jEv });
+               },
+               _main_template: function() {
+                       var t = this._super();
+                       t.children.push(this._actionsBar_template());
+                       return t;
+               },
+               _actionsBar_template: function() {
+                       return { tag: "DIV", cls: "pull-right", children: [
+                               new app.ui.Button({ label: "Cancel", onclick: 
this._close_handler }),
+                               new app.ui.Button({ label: "OK", onclick: 
this._commit_handler })
+                       ]};
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.MenuPanel = ui.AbstractPanel.extend({
+               defaults: {
+                       items: [],              // (required) an array of menu 
items
+                       modal: false
+               },
+               _baseCls: "uiMenuPanel",
+               init: function() {
+                       this._super();
+                       this.el = $(this._main_template());
+               },
+               open: function(jEv) {
+                       this._super(jEv);
+                       var cx = this; setTimeout(function() { 
$(document).bind("click", cx._close_handler); }, 50);
+               },
+               _getItems: function() {
+                       return this.config.items;
+               },
+               _close_handler: function(jEv) {
+                       this._super(jEv);
+                       $(document).unbind("click", this._close_handler);
+               },
+               _main_template: function() {
+                       return { tag: "DIV", cls: this._baseCls, children: 
this._getItems().map(this._menuItem_template, this) };
+               },
+               _menuItem_template: function(item) {
+                       var dx = item.disabled ? { onclick: function() {} } : 
{};
+                       return { tag: "LI", cls: "uiMenuPanel-item" + 
(item.disabled ? " disabled" : "") + (item.selected ? " selected" : ""), 
children: [ $.extend({ tag: "DIV", cls: "uiMenuPanel-label" }, item, dx ) ] };
+               },
+               _getPosition: function(jEv) {
+                       var right = !! 
$(jEv.target).parents(".pull-right").length;
+                       var parent = $(jEv.target).closest("BUTTON");
+                       return parent.vOffset()
+                               .addY(parent.vSize().y)
+                               .addX( right ? parent.vSize().x - 
this.el.vOuterSize().x : 0 )
+                               .asOffset();
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.SelectMenuPanel = ui.MenuPanel.extend({
+               defaults: {
+                       items: [],              // (required) an array of menu 
items
+                       value: null
+               },
+               _baseCls: "uiSelectMenuPanel uiMenuPanel",
+               init: function() {
+                       this.value = this.config.value;
+                       this._super();
+               },
+               _getItems: function() {
+                       return this.config.items.map( function( item ) {
+                               return {
+                                       text: item.text,
+                                       selected: this.value === item.value,
+                                       onclick: function( jEv ) {
+                                               var el = $( jEv.target 
).closest("LI");
+                                               
el.parent().children().removeClass("selected");
+                                               el.addClass("selected");
+                                               this.fire( "select", this, { 
value: item.value } );
+                                               this.value = item.value;
+                                       }.bind(this)
+                               };
+                       }, this );
+
+               }
+       });
+
+})( this.app );
+
+( function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.Table = ui.AbstractWidget.extend({
+               defaults: {
+                       store: null, // (required) implements interface 
app.data.DataSourceInterface
+                       height: 0,
+                       width: 0
+               },
+               _baseCls: "uiTable",
+               init: function(parent) {
+                       this._super();
+                       this.initElements(parent);
+                       this.config.store.on("data", this._data_handler);
+               },
+               attach: function(parent) {
+                       if(parent) {
+                               this._super(parent);
+                               this._reflow();
+                       }
+               },
+               initElements: function(parent) {
+                       this.el = $.joey(this._main_template());
+                       this.body = this.el.find(".uiTable-body");
+                       this.headers = this.el.find(".uiTable-headers");
+                       this.tools = this.el.find(".uiTable-tools");
+                       this.attach( parent );
+               },
+               _data_handler: function(store) {
+                       this.tools.text(store.summary);
+                       
this.headers.empty().append(this._header_template(store.columns));
+                       
this.body.empty().append(this._body_template(store.data, store.columns));
+                       this._reflow();
+               },
+               _reflow: function() {
+                       var firstCol = this.body.find("TR:first 
TH.uiTable-header-cell > DIV"),
+                                       headers = this.headers.find("TR:first 
TH.uiTable-header-cell > DIV");
+                       for(var i = 0; i < headers.length; i++) {
+                               $(headers[i]).width( $(firstCol[i]).width() );
+                       }
+                       this._scroll_handler();
+               },
+               _scroll_handler: function(ev) {
+                       
this.el.find(".uiTable-headers").scrollLeft(this.body.scrollLeft());
+               },
+               _dataClick_handler: function(ev) {
+                       var row = $(ev.target).closest("TR");
+                       if(row.length) {
+                               this.fire("rowClick", this, { row: row } );
+                       }
+               },
+               _headerClick_handler: function(ev) {
+                       var header = 
$(ev.target).closest("TH.uiTable-header-cell");
+                       if(header.length) {
+                               this.fire("headerClick", this, { header: 
header, column: header.data("column"), dir: header.data("dir") });
+                       }
+               },
+               _main_template: function() {
+                       return { tag: "DIV", id: this.id(), css: { width: 
this.config.width + "px" }, cls: this._baseCls, children: [
+                               { tag: "DIV", cls: "uiTable-tools" },
+                               { tag: "DIV", cls: "uiTable-headers", onclick: 
this._headerClick_handler },
+                               { tag: "DIV", cls: "uiTable-body",
+                                       onclick: this._dataClick_handler,
+                                       onscroll: this._scroll_handler,
+                                       css: { height: this.config.height + 
"px", width: this.config.width + "px" }
+                               }
+                       ] };
+               },
+               _header_template: function(columns) {
+                       var ret = { tag: "TABLE", children: [ 
this._headerRow_template(columns) ] };
+                       
ret.children[0].children.push(this._headerEndCap_template());
+                       return ret;
+               },
+               _headerRow_template: function(columns) {
+                       return { tag: "TR", cls: "uiTable-header-row", 
children: columns.map(function(column) {
+                               var dir = ((this.config.store.sort.column === 
column) && this.config.store.sort.dir) || "none";
+                               return { tag: "TH", data: { column: column, 
dir: dir }, cls: "uiTable-header-cell" + ((dir !== "none") ? " uiTable-sort" : 
""), children: [
+                                       { tag: "DIV", children: [
+                                               { tag: "DIV", cls: 
"uiTable-headercell-menu", text: dir === "asc" ? "\u25b2" : "\u25bc" },
+                                               { tag: "DIV", cls: 
"uiTable-headercell-text", text: column }
+                                       ]}
+                               ]};
+                       }, this)};
+               },
+               _headerEndCap_template: function() {
+                       return { tag: "TH", cls: "uiTable-headerEndCap", 
children: [ { tag: "DIV" } ] };
+               },
+               _body_template: function(data, columns) {
+                       return { tag: "TABLE", children: []
+                               .concat(this._headerRow_template(columns))
+                               .concat(data.map(function(row) {
+                                       return { tag: "TR", data: { row: row }, 
cls: "uiTable-row", children: columns.map(function(column){
+                                               return { tag: "TD", cls: 
"uiTable-cell", children: [ { tag: "DIV", text: (row[column] || "").toString() 
} ] };
+                                       })};
+                               }))
+                       };
+               }
+
+       });
+
+})( this.jQuery, this.app );
+
+( function( $, app, joey ) {
+
+       var ui = app.ns("ui");
+
+       var CELL_SEPARATOR = ",";
+       var CELL_QUOTE = '"';
+       var LINE_SEPARATOR = "\r\n";
+
+       ui.CSVTable = ui.AbstractWidget.extend({
+               defaults: {
+                       results: null
+               },
+               _baseCls: "uiCSVTable",
+               init: function( parent ) {
+                       this._super();
+                       var results = this.config.results.hits.hits;
+                       var columns = this._parseResults( results );
+                       this._downloadButton = new ui.Button({
+                               label: "Generate Download Link",
+                               onclick: this._downloadLinkGenerator_handler
+                       });
+                       this._downloadLink = $.joey( { tag: "A", text: 
"download", });
+                       this._downloadLink.hide();
+                       this._csvText = this._csv_template( columns, results );
+                       this.el = $.joey( this._main_template() );
+                       this.attach( parent );
+               },
+               _downloadLinkGenerator_handler: function() {
+                       this._downloadLink.attr("href", 
"data:text/csv;chatset=utf-8," + window.encodeURIComponent( this._csvText ) );
+                       this._downloadLink.show();
+               },
+               _parseResults: function( results ) {
+                       var columnPaths = {};
+                       (function parse( path, obj ) {
+                               if( obj instanceof Array ) {
+                                       for( var i = 0; i < obj.length; i++ ) {
+                                               parse( path, obj[i] );
+                                       }
+                               } else if( typeof obj === "object" ) {
+                                       for( var prop in obj ) {
+                                               parse( path + "." + prop, obj[ 
prop ] );
+                                       }
+                               } else {
+                                       columnPaths[ path ] = true;
+                               }
+                       })( "root", results );
+                       var columns = [];
+                       for( var column in columnPaths ) {
+                               columns.push( column.split(".").slice(1) );
+                       }
+                       return columns;
+               },
+               _main_template: function() { return (
+                       { tag: "DIV", cls: this._baseCls, id: this.id(), 
children: [
+                               this._downloadButton,
+                               this._downloadLink,
+                               { tag: "PRE", text: this._csvText }
+                       ] }
+               ); },
+               _csv_template: function( columns, results ) {
+                       return this._header_template( columns ) + 
LINE_SEPARATOR + this._results_template( columns, results );
+               },
+               _header_template: function( columns ) {
+                       return columns.map( function( column ) {
+                               return column.join(".");
+                       }).join( CELL_SEPARATOR );
+               },
+               _results_template: function( columns, results ) {
+                       return results.map( function( result ) {
+                               return columns.map( function( column ) {
+                                       var l = 0,
+                                               ptr = result;
+                                       while( l !== column.length && ptr != 
null ) {
+                                               ptr = ptr[ column[ l++ ] ];
+                                       }
+                                       return ( ptr == null ) ? "" : ( 
CELL_QUOTE + ptr.toString().replace(/"/g, '""') + CELL_QUOTE );
+                               }).join( CELL_SEPARATOR );
+                       }).join( LINE_SEPARATOR );
+               }
+       });
+
+})( this.jQuery, this.app, this.joey );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.JsonPretty = ui.AbstractWidget.extend({
+               defaults: {
+                       obj: null
+               },
+               init: function(parent) {
+                       this._super();
+                       this.el = $(this._main_template());
+                       this.attach(parent);
+                       this.el.click(this._click_handler);
+               },
+               
+               _click_handler: function(jEv) {
+                       var t = 
$(jEv.target).closest(".uiJsonPretty-name").closest("LI");
+                       if(t.length === 0 || 
t.parents(".uiJsonPretty-minimised").length > 0) { return; }
+                       t.toggleClass("uiJsonPretty-minimised");
+                       jEv.stopPropagation();
+               },
+               
+               _main_template: function() {
+                       try {
+                                       return { tag: "DIV", cls: 
"uiJsonPretty", children: this.pretty.parse(this.config.obj) };
+                       }       catch (error) {
+                                       throw "JsonPretty error: " + 
error.message;
+                       }
+               },
+               
+               pretty: { // from 
https://github.com/RyanAmos/Pretty-JSON/blob/master/pretty_json.js
+                       "expando" : function(value) {
+                               return (value && 
(/array|object/i).test(value.constructor.name)) ? "expando" : "";
+                       },
+                       "parse": function (member) {
+                               return this[(member == null) ? 'null' : 
member.constructor.name.toLowerCase()](member);
+                       },
+                       "null": function (value) {
+                               return this['value']('null', 'null');
+                       },
+                       "array": function (value) {
+                               var results = [];
+                               var lastItem = value.length - 1;
+                               value.forEach(function( v, i ) {
+                                       results.push({ tag: "LI", cls: 
this.expando(v), children: [ this['parse'](v) ] });
+                                       if( i !== lastItem ) {
+                                               results.push(",");
+                                       }
+                               }, this);
+                               return [ "[ ", ((results.length > 0) ? { tag: 
"UL", cls: "uiJsonPretty-array", children: results } : null), "]" ];
+                       },
+                       "object": function (value) {
+                               var results = [];
+                               var keys = Object.keys( value );
+                               var lastItem = keys.length - 1;
+                               keys.forEach( function( key, i ) {
+                                       var children = [ this['value']( 'name', 
'"' + key + '"' ), ": ", this['parse']( value[ key ]) ];
+                                       if( i !== lastItem ) {
+                                               children.push(",");
+                                       }
+                                       results.push( { tag: "LI", cls: 
this.expando( value[ key ] ), children: children } );
+                               }, this);
+                               return [ "{ ", ((results.length > 0) ? { tag: 
"UL", cls: "uiJsonPretty-object", children: results } : null ),  "}" ];
+                       },
+                       "number": function (value) {
+                               return this['value']('number', 
value.toString());
+                       },
+                       "string": function (value) {
+                               if 
(/^(http|https|file):\/\/[^\s]+$/.test(value)) {
+                                       return this['link']( value );
+                               } else {
+                                       return this['value']('string', '"' + 
value.toString() + '"');
+                               }
+                       },
+                       "boolean": function (value) {
+                               return this['value']('boolean', 
value.toString());
+                       },
+                       "link": function( value ) {
+                                       return this['value']("string", { tag: 
"A", href: value, target: "_blank", text: '"' + value + '"' } );
+                       },
+                       "value": function (type, value) {
+                               if 
(/^(http|https|file):\/\/[^\s]+$/.test(value)) {
+                               }
+                               return { tag: "SPAN", cls: "uiJsonPretty-" + 
type, text: value };
+                       }
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+       var ut = app.ns("ut");
+
+       ui.PanelForm = ui.AbstractWidget.extend({
+               defaults: {
+                       fields: null    // (required) instanceof 
app.ux.FieldCollection
+               },
+               init: function(parent) {
+                       this._super();
+                       this.el = $.joey(this._main_template());
+                       this.attach( parent );
+               },
+               _main_template: function() {
+                       return { tag: "DIV", id: this.id(), cls: "uiPanelForm", 
children: this.config.fields.fields.map(this._field_template, this) };
+               },
+               _field_template: function(field) {
+                       return { tag: "LABEL", cls: "uiPanelForm-field", 
children: [
+                               { tag: "DIV", cls: "uiPanelForm-label", 
children: [ field.label, ut.require_template(field) ] },
+                               field
+                       ]};
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( app ){
+
+       var ui = app.ns("ui");
+
+       ui.HelpPanel = ui.InfoPanel.extend({
+               defaults: {
+                       ref: "",
+                       open: true,
+                       autoRemove: true,
+                       modal: false,
+                       width: 500,
+                       height: 450,
+                       title: i18n.text("General.Help")
+               },
+               init: function() {
+                       this._super();
+                       this.body.append(i18n.text(this.config.ref));
+               }
+       });
+
+})( this.app );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.JsonPanel = ui.InfoPanel.extend({
+               defaults: {
+                       json: null, // (required)
+                       modal: false,
+                       open: true,
+                       autoRemove: true,
+                       height: 500,
+                       width: 600
+               },
+
+               _baseCls: "uiPanel uiInfoPanel uiJsonPanel",
+
+               _body_template: function() {
+                       var body = this._super();
+                       body.children = [ new ui.JsonPretty({ obj: 
this.config.json }) ];
+                       return body;
+               }
+       });
+
+})( this.app );
+
+(function( $, app, i18n ) {
+
+       var ui = app.ns("ui");
+
+       ui.SidebarSection = ui.AbstractWidget.extend({
+               defaults: {
+                       title: "",
+                       help: null,
+                       body: null,
+                       open: false
+               },
+               init: function() {
+                       this._super();
+                       this.el = $.joey( this._main_template() );
+                       this.body = this.el.children(".uiSidebarSection-body");
+                       this.config.open && ( this.el.addClass("shown") && 
this.body.css("display", "block") );
+               },
+               _showSection_handler: function( ev ) {
+                       var shown = $( ev.target ).closest(".uiSidebarSection")
+                               .toggleClass("shown")
+                                       
.children(".uiSidebarSection-body").slideToggle(200, function() { 
this.fire("animComplete", this); }.bind(this))
+                               .end()
+                               .hasClass("shown");
+                       this.fire(shown ? "show" : "hide", this);
+               },
+               _showHelp_handler: function( ev ) {
+                       new ui.HelpPanel({ref: this.config.help});
+                       ev.stopPropagation();
+               },
+               _main_template: function() { return (
+                       { tag: "DIV", cls: "uiSidebarSection", children: [
+                               (this.config.title && { tag: "DIV", cls: 
"uiSidebarSection-head", onclick: this._showSection_handler, children: [
+                                       this.config.title,
+                                       ( this.config.help && { tag: "SPAN", 
cls: "uiSidebarSection-help pull-right", onclick: this._showHelp_handler, text: 
i18n.text("General.HelpGlyph") } )
+                               ] }),
+                               { tag: "DIV", cls: "uiSidebarSection-body", 
children: [ this.config.body ] }
+                       ] }
+               ); }
+       });
+
+})( this.jQuery, this.app, this.i18n );
+
+(function( $, app ) {
+
+       var ui = app.ns("ui");
+
+       ui.ResultTable = ui.Table.extend({
+               defaults: {
+                       width: 500,
+                       height: 400
+               },
+
+               init: function() {
+                       this._super();
+                       this.on("rowClick", this._showPreview_handler);
+                       this.selectedRow = null;
+                       $(document).bind("keydown", this._nav_handler);
+               },
+               remove: function() {
+                       $(document).unbind("keydown", this._nav_handler);
+                       this._super();
+               },
+               attach: function(parent) {
+                       if(parent) {
+                               var height = parent.height() || ( 
$(document).height() - parent.offset().top - 41 ); // 41 = height in px of 
.uiTable-tools + uiTable-header
+                               var width = parent.width();
+                               this.el.width( width );
+                               this.body.width( width ).height( height );
+                       }
+                       this._super(parent);
+               },
+               showPreview: function(row) {
+                       row.addClass("selected");
+                       this.preview = new app.ui.JsonPanel({
+                               title: 
i18n.text("Browser.ResultSourcePanelTitle"),
+                               json: row.data("row")._source,
+                               onClose: function() { 
row.removeClass("selected"); }
+                       });
+               },
+               _nav_handler: function(jEv) {
+                       if(jEv.keyCode !== 40 && jEv.keyCode !== 38) {
+                               return;
+                       }
+                       this.selectedRow && this.preview && 
this.preview.remove();
+                       if(jEv.keyCode === 40) { // up arrow
+                               this.selectedRow = this.selectedRow ? 
this.selectedRow.next("TR") : this.body.find("TR:first");
+                       } else if(jEv.keyCode === 38) { // down arrow
+                               this.selectedRow = this.selectedRow ? 
this.selectedRow.prev("TR") : this.body.find("TR:last");
+                       }
+                       this.selectedRow && this.showPreview(this.selectedRow);
+               },
+               _showPreview_handler: function(obj, data) {
+                       this.showPreview(this.selectedRow = data.row);
+               }
+       });
+
+})( this.jQuery, this.app );
+
+(function( $, app, i18n ) {
+
+       var ui = app.ns("ui");
+       var ut = app.ns("ut");
+
+       ui.QueryFilter = ui.AbstractWidget.extend({
+               defaults: {
+                       metadata: null,   // (required) instanceof 
app.data.MetaData
+                       query: null       // (required) instanceof 
app.data.Query that the filters will act apon
+               },
+               init: function() {
+                       this._super();
+                       this.metadata = this.config.metadata;
+                       this.query = this.config.query;
+                       this.el = $(this._main_template());
+               },
+               helpTypeMap: {
+                       "date" : "QueryFilter.DateRangeHelp"
+               },
+               requestUpdate: function(jEv) {
+                       if(jEv && jEv.originalEvent) { // we only want to 
update on real user interaction not generated events
+                               this.query.setPage(1);
+                               this.query.query();
+                       }
+               },
+               getSpec: function(fieldName) {
+                       var fieldNameParts = fieldName.split('.');
+                       var namePart = 0;
+                       var spec = 
this.metadata.fields[fieldNameParts[namePart]];
+                       while (typeof spec.fields !== "undefined") {
+                               namePart++;
+                               if (typeof 
spec.fields[fieldNameParts[namePart]] === "undefined") {
+                                       break;
+                               }
+                               spec =  spec.fields[fieldNameParts[namePart]];
+                       }
+                       return spec;
+               },
+               _selectAlias_handler: function(jEv) {
+                       var indices = (jEv.target.selectedIndex === 0) ? [] : 
this.metadata.getIndices($(jEv.target).val());
+                       $(".uiQueryFilter-index").each(function(i, el) {
+                               var jEl = $(el);
+                               if(indices.contains(jEl.text()) !== 
jEl.hasClass("selected")) {
+                                       jEl.click();
+                               }
+                       });
+                       this.requestUpdate(jEv);
+               },
+               _selectIndex_handler: function(jEv) {
+                       var jEl = $(jEv.target).closest(".uiQueryFilter-index");
+                       jEl.toggleClass("selected");
+                       var selected = jEl.hasClass("selected");
+                       this.query.setIndex(jEl.text(), selected);
+                       if(selected) {
+                               var types = 
this.metadata.getTypes(this.query.indices);
+                               
this.el.find("DIV.uiQueryFilter-type.selected").each(function(n, el) {
+                                       if(! types.contains($(el).text())) {
+                                               $(el).click();
+                                       }
+                               });
+                       }
+                       this.requestUpdate(jEv);
+               },
+               _selectType_handler: function(jEv) {
+                       var jEl = $(jEv.target).closest(".uiQueryFilter-type");
+                       jEl.toggleClass("selected");
+                       var type = jEl.text(), selected = 
jEl.hasClass("selected");
+                       this.query.setType(type, selected);
+                       if(selected) {
+                               var indices = this.metadata.types[type].indices;
+                               // es throws a 500 if searching an index for a 
type it does not contain - so we prevent that
+                               
this.el.find("DIV.uiQueryFilter-index.selected").each(function(n, el) {
+                                       if(! indices.contains($(el).text())) {
+                                               $(el).click();
+                                       }
+                               });
+                               // es throws a 500 if you specify types from 
different indices with _all
+                               
jEl.siblings(".uiQueryFilter-type.selected").forEach(function(el) {
+                                       
if(this.metadata.types[$(el).text()].indices.intersection(indices).length === 
0) {
+                                               $(el).click();
+                                       }
+                               }, this);
+                       }
+                       this.requestUpdate(jEv);
+               },
+               _openFilter_handler: function(section) {
+                       var field_name = section.config.title;
+                       if(! section.loaded) {
+                               var spec = this.getSpec(field_name);
+                               if(spec.core_type === "string") {
+                                       
section.body.append(this._textFilter_template(spec));
+                               } else if(spec.core_type === "date") {
+                                       
section.body.append(this._dateFilter_template(spec));
+                                       section.body.append(new 
ui.DateHistogram({ printEl: section.body.find("INPUT"), cluster: this.cluster, 
query: this.query, spec: spec }));
+                               } else if(spec.core_type === "number") {
+                                       
section.body.append(this._numericFilter_template(spec));
+                               } else if(spec.core_type === 'boolean') {
+                                       
section.body.append(this._booleanFilter_template(spec));
+                               } else if (spec.core_type === 'multi_field') {
+                                       
section.body.append(this._multiFieldFilter_template(section, spec));
+                               } 
+                               section.loaded = true;
+                       }
+                       section.on("animComplete", function(section) { 
section.body.find("INPUT").focus(); });
+               },
+               _textFilterChange_handler: function(jEv) {
+                       var jEl = $(jEv.target).closest("INPUT");
+                       var val = jEl.val();
+                       var spec = jEl.data("spec");
+                       var uqids = jEl.data("uqids") || [];
+                       uqids.forEach(function(uqid) {
+                               uqid && this.query.removeClause(uqid);
+                       }, this);
+                       if(val.length) {
+                               if(jEl[0] === document.activeElement && 
jEl[0].selectionStart === jEl[0].selectionEnd) {
+                                       val = val.replace(new 
RegExp("(.{"+jEl[0].selectionStart+"})"), "$&*");
+                               }
+                               uqids = val.split(/\s+/).map(function(term) {
+                                       // Figure out the actual field name - 
needed for multi_field, because
+                                       // querying for "field.field" will not 
work. Simply "field" must be used
+                                       // if nothing is aliased.
+                                       var fieldNameParts = 
spec.field_name.split('.');
+                                       var part = fieldNameParts.length - 1;
+                                       var name = fieldNameParts[part];
+                                       while (part >= 1) {
+                                               if (fieldNameParts[part] !== 
fieldNameParts[part - 1]) {
+                                                       name = 
fieldNameParts[part - 1] + "." + name;
+                                               }
+                                               part--;
+                                       }
+                                       return term && 
this.query.addClause(term, name, "wildcard", "must");
+                               }, this);
+                       }
+                       jEl.data("uqids", uqids);
+                       this.requestUpdate(jEv);
+               },
+               _dateFilterChange_handler: function(jEv) {
+                       var jEl = $(jEv.target).closest("INPUT");
+                       var val = jEl.val();
+                       var spec = jEl.data("spec");
+                       var uqid = jEl.data("uqid") || null;
+                       var range = window.dateRangeParser.parse(val);
+                       var lastRange = jEl.data("lastRange");
+                       if(!range || (lastRange && lastRange.start === 
range.start && lastRange.end === range.end)) {
+                               return;
+                       }
+                       uqid && this.query.removeClause(uqid);
+                       if((range.start && range.end) === null) {
+                               uqid = null;
+                       } else {
+                               var value = {};
+                               if( range.start ) {
+                                       value["gte"] = range.start;
+                               }
+                               if( range.end ) {
+                                       value["lte"] = range.end;
+                               }
+                               uqid = this.query.addClause( value, 
spec.field_name, "range", "must");
+                       }
+                       jEl.data("lastRange", range);
+                       jEl.siblings(".uiQueryFilter-rangeHintFrom")
+                               
.text(i18n.text("QueryFilter.DateRangeHint.from", range.start && new 
Date(range.start).toUTCString()));
+                       jEl.siblings(".uiQueryFilter-rangeHintTo")
+                               .text(i18n.text("QueryFilter.DateRangeHint.to", 
range.end && new Date(range.end).toUTCString()));
+                       jEl.data("uqid", uqid);
+                       this.requestUpdate(jEv);
+               },
+               _numericFilterChange_handler: function(jEv) {
+                       var jEl = $(jEv.target).closest("INPUT");
+                       var val = jEl.val();
+                       var spec = jEl.data("spec");
+                       var uqid = jEl.data("uqid") || null;
+                       var lastRange = jEl.data("lastRange");
+                       var range = (function(val) {
+                               var ops = val.split(/->|<>|</).map( function(v) 
{ return parseInt(v.trim(), 10); });
+                               if(/<>/.test(val)) {
+                                       return { gte: (ops[0] - ops[1]), lte: 
(ops[0] + ops[1]) };
+                               } else if(/->|</.test(val)) {
+                                       return { gte: ops[0], lte: ops[1] };
+                               } else {
+                                       return { gte: ops[0], lte: ops[0] };
+                               }
+                       })(val || "");
+                       if(!range || (lastRange && lastRange.lte === range.lte 
&& lastRange.gte === range.gte)) {
+                               return;
+                       }
+                       jEl.data("lastRange", range);
+                       uqid && this.query.removeClause(uqid);
+                       uqid = this.query.addClause( range, spec.field_name, 
"range", "must");
+                       jEl.data("uqid", uqid);
+                       this.requestUpdate(jEv);
+               },
+               _booleanFilterChange_handler: function( jEv ) {
+                       var jEl = $(jEv.target).closest("SELECT");
+                       var val = jEl.val();
+                       var spec = jEl.data("spec");
+                       var uqid = jEl.data("uqid") || null;
+                       uqid && this.query.removeClause(uqid);
+                       if(val === "true" || val === "false") {
+                               jEl.data("uqid", this.query.addClause(val, 
spec.field_name, "term", "must") );
+                       }
+                       this.requestUpdate(jEv);
+               },
+               _main_template: function() {
+                       return { tag: "DIV", id: this.id(), cls: 
"uiQueryFilter", children: [
+                               this._aliasSelector_template(),
+                               this._indexSelector_template(),
+                               this._typesSelector_template(),
+                               this._filters_template()
+                       ] };
+               },
+               _aliasSelector_template: function() {
+                       var aliases = Object.keys(this.metadata.aliases).sort();
+                       aliases.unshift( i18n.text("QueryFilter.AllIndices") );
+                       return { tag: "DIV", cls: "uiQueryFilter-section 
uiQueryFilter-aliases", children: [
+                               { tag: "SELECT", onChange: 
this._selectAlias_handler, children: aliases.map(ut.option_template) }
+                       ] };
+               },
+               _indexSelector_template: function() {
+                       var indices = Object.keys( this.metadata.indices 
).sort();
+                       return { tag: "DIV", cls: "uiQueryFilter-section 
uiQueryFilter-indices", children: [
+                               { tag: "HEADER", text: 
i18n.text("QueryFilter-Header-Indices") },
+                               { tag: "DIV", onClick: 
this._selectIndex_handler, children: indices.map( function( name ) {
+                                       return { tag: "DIV", cls: 
"uiQueryFilter-booble uiQueryFilter-index", text: name };
+                               })}
+                       ] };
+               },
+               _typesSelector_template: function() {
+                       var types = Object.keys( this.metadata.types ).sort();
+                       return { tag: "DIV", cls: "uiQueryFilter-section 
uiQueryFilter-types", children: [
+                               { tag: "HEADER", text: 
i18n.text("QueryFilter-Header-Types") },
+                               { tag: "DIV", onClick: 
this._selectType_handler, children: types.map( function( name ) {
+                                       return { tag: "DIV", cls: 
"uiQueryFilter-booble uiQueryFilter-type", text: name };
+                               })}
+                       ] };
+               },
+               _filters_template: function() {
+                       var fields = Object.keys( this.metadata.fields ).sort();
+                       return { tag: "DIV", cls: "uiQueryFilter-section 
uiQueryFilter-filters", children: [
+                               { tag: "HEADER", text: 
i18n.text("QueryFilter-Header-Fields") },
+                               { tag: "DIV", children: fields.map( 
function(name ) {
+                                       return new app.ui.SidebarSection({
+                                               title: name,
+                                               help: 
this.helpTypeMap[this.metadata.fields[ name ].type],
+                                               onShow: this._openFilter_handler
+                                       });
+                               }, this ) }
+                       ] };
+               },
+               _textFilter_template: function(spec) {
+                       return { tag: "INPUT", data: { spec: spec }, onKeyup: 
this._textFilterChange_handler };
+               },
+               _dateFilter_template: function(spec) {
+                       return { tag: "DIV", children: [
+                               { tag: "INPUT", data: { spec: spec }, onKeyup: 
this._dateFilterChange_handler },
+                               { tag: "PRE", cls: 
"uiQueryFilter-rangeHintFrom", text: 
i18n.text("QueryFilter.DateRangeHint.from", "")},
+                               { tag: "PRE", cls: "uiQueryFilter-rangeHintTo", 
text: i18n.text("QueryFilter.DateRangeHint.to", "") }
+                       ]};
+               },
+               _numericFilter_template: function(spec) {
+                       return { tag: "INPUT", data: { spec: spec }, onKeyup: 
this._numericFilterChange_handler };
+               },
+               _booleanFilter_template: function(spec) {
+                       return { tag: "SELECT", data: { spec: spec }, onChange: 
this._booleanFilterChange_handler,
+                               children: [ i18n.text("QueryFilter.AnyValue"), 
"true", "false" ].map( function( val ) {
+                                       return { tag: "OPTION", value: val, 
text: val };
+                               })
+                       };
+               },
+               _multiFieldFilter_template: function(section, spec) {
+                       return {
+                               tag : "DIV", cls : 
"uiQueryFilter-subMultiFields", children : acx.eachMap(spec.fields, 
function(name, data) {
+                                       if (name === spec.field_name) {
+                                               section.config.title = 
spec.field_name + "." + name;
+                                               return 
this._openFilter_handler(section);
+                                       }
+                                       return new app.ui.SidebarSection({
+                                               title : data.field_name, help : 
this.helpTypeMap[data.type], onShow : this._openFilter_handler
+                                       });
+                               }, this)
+                       };
+               }       
+       });
+
+})( this.jQuery, this.app, this.i18n );
+
+(function( app ) {
+
+       var ui = app.ns("ui");
+
+       ui.Page = ui.AbstractWidget.extend({
+               show: function() {
+                       this.el.show();
+               },
+               hide: function() {
+                       this.el.hide();
+               }
+       });
+
+})( this.app );
+(function( $, app, i18n ){
+
+       var ui = app.ns("ui");
+       var data = app.ns("data");
+
+       ui.Browser = ui.Page.extend({
+               defaults: {
+                       cluster: null  // (required) instanceof 
app.services.Cluster
+               },
+               init: function() {
+                       this._super();
+                       this.cluster = this.config.cluster;
+                       this.query = new app.data.Query( { cluster: 
this.cluster } );
+                       this._refreshButton = new ui.Button({
+                               label: i18n.text("General.RefreshResults"),
+                               onclick: function( btn ) {
+                                       this.query.query();
+                               }.bind(this)
+                       });
+                       this.el = $(this._main_template());
+                       new data.MetaDataFactory({
+                               cluster: this.cluster,
+                               onReady: function(metadata) {
+                                       this.metadata = metadata;
+                                       this.store = new 
data.QueryDataSourceInterface( { metadata: metadata, query: this.query } );
+                                       this.queryFilter = new ui.QueryFilter({ 
metadata: metadata, query: this.query });
+                                       this.queryFilter.attach(this.el.find("> 
.uiBrowser-filter") );
+                                       this.resultTable = new ui.ResultTable( {
+                                               onHeaderClick: 
this._changeSort_handler,
+                                               store: this.store
+                                       } );
+                                       this.resultTable.attach( 
this.el.find("> .uiBrowser-table") );
+                                       this.updateResults();
+                               }.bind(this)
+                       });
+               },
+               updateResults: function() {
+                       this.query.query();
+               },
+               _changeSort_handler: function(table, wEv) {
+                       this.query.setSort(wEv.column, wEv.dir === "desc");
+                       this.query.setPage(1);
+                       this.query.query();
+               },
+               _main_template: function() {
+                       return { tag: "DIV", cls: "uiBrowser", children: [
+                               new ui.Toolbar({
+                                       label: i18n.text("Browser.Title"),
+                                       left: [ ],
+                                       right: [ this._refreshButton ]
+                               }),
+                               { tag: "DIV", cls: "uiBrowser-filter" },
+                               { tag: "DIV", cls: "uiBrowser-table" }
+                       ] };
+               }
+       });
+
+})( this.jQuery, this.app, this.i18n );
+
+(function( $, app, i18n, raphael ) {
+
+       var ui = app.ns("ui");
+       var ut = app.ns("ut");
+       var services = app.ns("services");
+
+       ui.AnyRequest = ui.Page.extend({
+               defaults: {
+                       cluster: null,       // (required) instanceof 
app.services.Cluster
+                       path: "_search",     // default uri to send a request to
+                       query: { query: { match_all: { }}},
+                       transform: "  return root;" // default transformer 
function (does nothing)
+               },
+               init: function(parent) {
+                       this._super();
+                       this.prefs = services.Preferences.instance();
+                       this.history = this.prefs.get("anyRequest-history") || 
[ { type: "POST", path: this.config.path, query : 
JSON.stringify(this.config.query), transform: this.config.transform } ];
+                       this.el = $.joey(this._main_template());
+                       this.base_uriEl = this.el.find("INPUT[name=base_uri]");
+                       this.pathEl = this.el.find("INPUT[name=path]");
+                       this.typeEl = this.el.find("SELECT[name=method]");
+                       this.dataEl = this.el.find("TEXTAREA[name=body]");
+                       this.prettyEl = this.el.find("INPUT[name=pretty]");
+                       this.transformEl = 
this.el.find("TEXTAREA[name=transform]");
+                       this.asGraphEl = this.el.find("INPUT[name=asGraph]");
+                       this.asTableEl = this.el.find("INPUT[name=asTable]");
+                       this.asJsonEl = this.el.find("INPUT[name=asJson]");
+                       this.cronEl = this.el.find("SELECT[name=cron]");
+                       this.outEl = this.el.find("DIV.uiAnyRequest-out");
+                       this.errEl = this.el.find("DIV.uiAnyRequest-jsonErr");
+                       this.typeEl.val("GET");
+                       this.attach(parent);
+                       this.setHistoryItem(this.history[this.history.length - 
1]);
+               },
+               setHistoryItem: function(item) {
+                       this.pathEl.val(item.path);
+                       this.typeEl.val(item.type);
+                       this.dataEl.val(item.query);
+                       this.transformEl.val(item.transform);
+               },
+               _request_handler: function( ev ) {
+                       if(! this._validateJson_handler()) {
+                               return;
+                       }
+                       var path = this.pathEl.val(),
+                                       type = this.typeEl.val(),
+                                       query = 
JSON.stringify(JSON.parse(this.dataEl.val())),
+                                       transform = this.transformEl.val(),
+                                       base_uri = this.base_uriEl.val();
+                       if( ev ) { // if the user click request
+                               if(this.timer) {
+                                       window.clearTimeout(this.timer); // 
stop any cron jobs
+                               }
+                               delete this.prevData; // remove data from 
previous cron runs
+                               
this.outEl.text(i18n.text("AnyRequest.Requesting"));
+                               if( ! /\/$/.test( base_uri )) {
+                                       base_uri += "/";
+                                       this.base_uriEl.val( base_uri );
+                               }
+                               for(var i = 0; i < this.history.length; i++) {
+                                       if(this.history[i].path === path &&
+                                               this.history[i].type === type &&
+                                               this.history[i].query === query 
&&
+                                               this.history[i].transform === 
transform) {
+                                               this.history.splice(i, 1);
+                                       }
+                               }
+                               this.history.push({
+                                       path: path,
+                                       type: type,
+                                       query: query,
+                                       transform: transform
+                               });
+                               this.history.slice(250); // make sure history 
does not get too large
+                               this.prefs.set( "anyRequest-history", 
this.history );
+                               this.el.find("UL.uiAnyRequest-history")
+                                       .empty()
+                                       .append($( { tag: "UL", children: 
this.history.map(this._historyItem_template, this) }).children())
+                                       
.children().find(":last-child").each(function(i, j) { j.scrollIntoView(false); 
}).end()
+                                       .scrollLeft(0);
+                       }
+                       this.config.cluster.request({
+                               url: base_uri + path,
+                               type: type,
+                               data: query,
+                               success: this._responseWriter_handler,
+                               error: this._responseError_handler
+                       });
+               },
+               _responseError_handler: function (response) {
+                       var obj;
+                       try {
+                               obj = JSON.parse(response.responseText);
+                               if (obj) {
+                                       this._responseWriter_handler(obj);
+                               }
+                       } catch (err) {
+                       }
+               },
+               _responseWriter_handler: function(data) {
+                       this.outEl.empty();
+                       try {
+                               data = (new Function("root", "prev", 
this.transformEl.val()))(data, this.

<TRUNCATED>
http://git-wip-us.apache.org/repos/asf/karaf-decanter/blob/c4719b9d/elasticsearch-head/src/main/resources/base/favicon.png
----------------------------------------------------------------------
diff --git a/elasticsearch-head/src/main/resources/base/favicon.png 
b/elasticsearch-head/src/main/resources/base/favicon.png
new file mode 100644
index 0000000..f433ec6
Binary files /dev/null and 
b/elasticsearch-head/src/main/resources/base/favicon.png differ

Reply via email to