// reference local blank image
Ext.BLANK_IMAGE_URL = 'extjs/lib/resources/images/default/s.gif';

// load plugins

/*
 * Plugins
 */

function roundNumber(num, dec) {
  var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
  return result;
}

function trim(str, chars) {
    return ltrim(rtrim(str, chars), chars);
}

function ltrim(str, chars) {
    chars = chars || "\\s";
    return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}

function rtrim(str, chars) {
    chars = chars || "\\s";
    return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

/* Ext.Container.removeAll() */
Ext.override(Ext.Container, {
  removeAll : function(autoDestroy){
    var item, removed=[];
    while(this.items && ( item = this.items.last()) ){
      removed.unshift ( this.remove(item,autoDestroy) );
    }
    if(!!removed.length) { this.doLayout(); }
    //an array in original layout order
    return !!removed.length ? removed : null;
  }
});


var imgButtonTpl = new Ext.Template(
  '<table border="0" cellpadding="0" cellspacing="0"><tbody><tr>' +
  '<td><i> </i></td><td>' +
  '<button type="button"><img src="{0}"></button>' +
  '</td><td ><i> </i></td>' +
  '</tr></tbody></table>');

Ext.ux.collapsedPanelTitlePlugin = function ()
{
  this.init = function(p) {
    if (p.collapsible)
    {
      var r = p.region;
      if ((r == 'north') || (r == 'south'))
      {
        p.on ('render', function()
          {
              var ct = p.ownerCt;
              ct.on ('afterlayout', function()
                {
                  if (ct.layout[r].collapsedEl)
                  {
                      p.collapsedTitleEl = ct.layout[r].collapsedEl.createChild ({
                          tag: 'span',
                          cls: 'x-panel-collapsed-text',
                          html: p.title
                      });
                      p.setTitle = Ext.Panel.prototype.setTitle.createSequence (function(t)
                          {p.collapsedTitleEl.dom.innerHTML = t;});
                  }
              }, false, {single:true});
          p.on ('collapse', function()
              {
                  if (ct.layout[r].collapsedEl && !p.collapsedTitleEl)
                  {
                      p.collapsedTitleEl = ct.layout[r].collapsedEl.createChild ({
                          tag: 'span',
                          cls: 'x-panel-collapsed-text',
                          html: p.title
                      });
                      p.setTitle = Ext.Panel.prototype.setTitle.createSequence (function(t)
                          {p.collapsedTitleEl.dom.innerHTML = t;});
                  }
                }, false, {single:true});
          });
      }
    }
  };
};

Ext.Ajax.on('requestcomplete', function( oConn, oResp, oOpts ) {
  var res = Ext.decode(oResp.responseText);

  if (res.sfException)
  {
    Ext.Msg.alert('Error', res.sfException);
  }
});

Ext.Ajax.on('requestexception', function( oConn, oResp, oOpts ) {
  var res = Ext.decode(oResp.responseText);

  if (res.sfException)
  {
    Ext.Msg.alert('Error', res.sfException);
  }
});

Ext.Msg.info = function(title,msg,minWidth){
  
  if (!minWidth) minWidth = 300;
  
  Ext.Msg.show({
    title: title,
    msg: msg,
    buttons: Ext.Msg.OK,
    icon: Ext.MessageBox.INFO,
    minWidth: minWidth
  });
}

Ext.Msg.warning = function(title,msg,minWidth){
  
  if (!minWidth) minWidth = 300;
  
  Ext.Msg.show({
    title: title,
    msg: msg,
    buttons: Ext.Msg.OK,
    icon: Ext.MessageBox.WARNING,
    minWidth: minWidth
  });
}

Ext.Msg.error = function(title,msg,minWidth){
  
  if (!minWidth) minWidth = 300;
  
  Ext.Msg.show({
    title: title,
    msg: msg,
    buttons: Ext.Msg.OK,
    icon: Ext.MessageBox.ERROR,
    minWidth: minWidth
  });
}

Ext.Msg.showRequestErrors = function(r){
  var errors = '';
  
  for (error in r.errors)
  {
    errors+= r.errors[error] + '<br/>';
  }
  
  Ext.Msg.show({
    title: 'Error',
    msg: errors,
    buttons: Ext.Msg.OK,
    icon: Ext.MessageBox.WARNING,
    minWidth: 400
  });
}
Ext.override(Ext.Panel, {
  setIconCls: function(i){
    Ext.fly(this.ownerCt.getTabEl(this)).child('.x-tab-strip-text').replaceClass(this.iconCls, i);
    this.setIconClass(i);
  }
});

//make components only save state when told to by stateful = true
Ext.override(Ext.Component, {
    saveState : function(){
        if(Ext.state.Manager && this.stateful !== false){
            var state = this.getState();
            if(this.fireEvent('beforestatesave', this, state) !== false){
                Ext.state.Manager.set(this.stateId || this.id, state);
                this.fireEvent('statesave', this, state);
            }
        }
    },
 
    stateful : false
 
});

//decodeValue erroneously decodes empty arrays and objects
//empty arrays return as [undefined]
//empty objects (untested) probably fail or return as {undefined: undefined}
//either way, a simple check for empty value portions alleviates this issue
Ext.override(Ext.state.Provider, {
    decodeValue : function(cookie){
        var re = /^(a|n|d|b|s|o)\:(.*)$/;
        var matches = re.exec(unescape(cookie));
        if(!matches || !matches[1]) return; // non state cookie
        var type = matches[1];
        var v = matches[2];
        switch(type){
            case "n":
                return parseFloat(v);
            case "d":
                return new Date(Date.parse(v));
            case "b":
                return (v == "1");
            case "a":
                var all = [];
                if (v) {
                    var values = v.split("^");
                    for(var i = 0, len = values.length; i < len; i++){
                        all.push(this.decodeValue(values[i]));
                    }
                }
                return all;
           case "o":
                var all = {};
                if (v) {
                    var values = v.split("^");
                    for(var i = 0, len = values.length; i < len; i++){
                        var kv = values[i].split("=");
                        all[kv[0]] = this.decodeValue(kv[1]);
                    }
                }
                return all;
           default:
                return v;
        }
    }
});

//Panels added to the portal have their onResize function called twice.  Once with width and height on creation,
//and again with only width when put into the ColumnLayout.  If a panel is collapsed at creation, then the
//queuedBodySize object ends up with only the second call's data for width and height, effectively making panels
//that start collapsed autoSized when expanded the first time.
Ext.override(Ext.Panel, {
    onResize : function(w, h){
        if(w !== undefined || h !== undefined){
            if(!this.collapsed){
                if(typeof w == 'number'){
                    this.body.setWidth(
                            this.adjustBodyWidth(w - this.getFrameWidth()));
                }else if(w == 'auto'){
                    this.body.setWidth(w);
                }

                if(typeof h == 'number'){
                    this.body.setHeight(
                            this.adjustBodyHeight(h - this.getFrameHeight()));
                }else if(h == 'auto'){
                    this.body.setHeight(h);
                }
            }else{
                //these two lines are the primary fix.
                if (!this.queuedBodySize) this.queuedBodySize = {};
                this.queuedBodySize = {width: w || this.queuedBodySize.width, height: h || this.queuedBodySize.height};
                if(!this.queuedExpand && this.allowQueuedExpand !== false){
                    this.queuedExpand = true;
                    //switched this from expand to beforeexpand to keep the panel
                    //from expanding to full size, then popping back down to the correct size.
                    this.on('beforeexpand', function(){
                        delete this.queuedExpand;
                        this[this.collapseEl].show();
                        this.collapsed = false;
                        this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
                        this.doLayout();
                    }, this, {single:true});
                }
            }
            this.fireEvent('bodyresize', this, w, h);
        }
        this.syncShadow();
    }
});

/* GridRowChecker */

Ext.ux.GridRowChecker = Ext.extend(Object, {
      header: "",
      width: 23,
      sortable: false,
      fixed: true,
      menuDisabled: true,
      dataIndex: '',
      id: 'selection-checkbox',
      rowspan: undefined,

      init: function(grid) {
        this.grid = grid;
        this.gridSelModel = this.grid.getSelectionModel();
        this.gridSelModel.originalMouseDown = this.gridSelModel.handleMouseDown;
      this.gridSelModel.handleMouseDown = this.onGridMouseDown;
        grid.getColumnModel().config.unshift(this);
        grid.getChecked = this.getChecked.createDelegate(this);
        grid.checkAll = this.checkAll.createDelegate(this);
        grid.uncheckAll = this.uncheckAll.createDelegate(this);
      },

    renderer: function() {
      return '<input class="x-row-checkbox" type="checkbox">';
    },

    getChecked: function() {
      var result = [];
      var cb = this.grid.getEl().query("div.x-grid3-col-selection-checkbox > input[type=checkbox]");
      var idx = 0;
      this.grid.store.each(function(rec) {
        if (cb[idx++].checked) {
          result.push(rec);
        }
      });
      delete cb;
      return result;
    },

    checkAll: function() {
      this.grid.getEl().select("div.x-grid3-col-selection-checkbox > input[type='checkbox']").each(function(e){
        e.dom.checked = true;
      });
    },

    uncheckAll: function() {
      this.grid.getEl().select("div.x-grid3-col-selection-checkbox > input[type='checkbox']").each(function(e){
        e.dom.checked = false;
      });
    },

    onGridMouseDown: function(g, rowIndex, e) {
      if (e.getTarget('div.x-grid3-col-selection-checkbox')) {
        e.stopEvent();
        return false;
      }
        this.originalMouseDown.apply(this, arguments);
    }
    });

/* emptyText fix */
Ext.form.Action.Submit.prototype.run = Ext.form.Action.Submit.prototype.run.createInterceptor(function() {
  this.form.items.each(function(item) {

    if (!item.el) return false;
    
    if (item.el.getValue() == item.emptyText) {
      item.el.dom.value = '';
    }
  });
});
 
Ext.form.Action.Submit.prototype.run = Ext.form.Action.Submit.prototype.run.createSequence(function() {
  this.form.items.each(function(item) {
    
    if (!item.el) return false;
    
    if (item.el.getValue() == '' && item.emptyText) {
      item.el.dom.value = item.emptyText;
    }
  });
 });
/**
  * @class Ext.ux.XCheckbox
  * @extends Ext.form.Checkbox
  */
Ext.ns('Ext.ux.form');
Ext.ux.form.XCheckbox = Ext.extend(Ext.form.Checkbox, {
     submitOffValue: 0
    ,submitOnValue: 1

    ,onRender:function() {

        this.inputValue = this.submitOnValue;

        // call parent
        Ext.ux.form.XCheckbox.superclass.onRender.apply(this, arguments);

        // create hidden field that is submitted if checkbox is not checked
        this.hiddenField = this.wrap.insertFirst({tag:'input', type:'hidden'});

        // support tooltip
        if(this.tooltip) {
            this.imageEl.set({qtip:this.tooltip});
        }

        // update value of hidden field
        this.updateHidden();

    } // eo function onRender

    /**
     * Calls parent and updates hiddenField
     * @private
     */
    ,setValue:function(v) {
        this.updateHidden(v);
        Ext.ux.form.XCheckbox.superclass.setValue.apply(this, arguments);
    } // eo function setValue

    /**
     * Updates hiddenField
     * @private
     */
    ,updateHidden:function(v) {
        v = undefined !== v ? v : this.checked;
        v = (v === true || v === 'true' || v === '1' || String(v).toLowerCase() == 'on');
        if(this.hiddenField) {
            this.hiddenField.dom.value = v ? this.submitOnValue : this.submitOffValue;
            this.hiddenField.dom.name = v ? '' : this.el.dom.name;
        }
    } // eo function updateHidden

}); // eo extend

// register xtype
Ext.reg('xcheckbox', Ext.ux.form.XCheckbox);

/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.menu.EditableItem = Ext.extend(Ext.menu.BaseItem, {
    itemCls : "x-menu-item",
    hideOnClick: false,
    
    initComponent: function(){
      Ext.menu.EditableItem.superclass.initComponent.call(this);
    	this.addEvents('keyup');
    	
			this.editor = this.editor || new Ext.form.TextField();
			if(this.text) {
				this.editor.setValue(this.text);
      }
    },
    
    onRender: function(container){
        var s = container.createChild({
        	cls: this.itemCls,
        	html: '<img src="' + this.icon + '" class="x-menu-item-icon" style="margin: 3px 3px 2px 2px;" />'
        });
        
        Ext.apply(this.config, {width: 125});
        this.editor.render(s);
        
        this.el = s;
        this.relayEvents(this.editor.el, ["keyup"]);
        
        if(Ext.isGecko) {
    			s.setStyle('overflow', 'auto');
        }
			
        Ext.menu.EditableItem.superclass.onRender.call(this, container);
    },
    
    getValue: function(){
    	return this.editor.getValue();
    },
    
    setValue: function(value){
    	this.editor.setValue(value);
    },
    
    isValid: function(preventMark){
    	return this.editor.isValid(preventMark);
    }
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.menu.RangeMenu = function(config){
	Ext.menu.RangeMenu.superclass.constructor.call(this, config);
  
	this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);

	var cfg = this.fieldCfg;
	var cls = this.fieldCls;
	var fields = this.fields = Ext.applyIf(this.fields || {}, {
		'gt': new Ext.menu.EditableItem({
			icon:  this.icons.gt,
			editor: new cls(typeof cfg == "object" ? cfg.gt || '' : cfg)
    }),
		'lt': new Ext.menu.EditableItem({
			icon:  this.icons.lt,
			editor: new cls(typeof cfg == "object" ? cfg.lt || '' : cfg)
    }),
		'eq': new Ext.menu.EditableItem({
			icon:   this.icons.eq, 
			editor: new cls(typeof cfg == "object" ? cfg.gt || '' : cfg)
    })
	});
	this.add(fields.gt, fields.lt, '-', fields.eq);
	
	for(var key in fields) {
		fields[key].on('keyup', this.onKeyUp.createDelegate(this, [fields[key]], true), this);
  }
  
	this.addEvents('update');
};

Ext.extend(Ext.menu.RangeMenu, Ext.menu.Menu, {
	fieldCls:     Ext.form.NumberField,
	fieldCfg:     '',
	updateBuffer: 500,
	icons: {
		gt: '/img/small_icons/greater_then.png', 
		lt: '/img/small_icons/less_then.png',
		eq: '/img/small_icons/equals.png'
  },
		
	fireUpdate: function() {
		this.fireEvent("update", this);
	},
	
	setValue: function(data) {
		for(var key in this.fields) {
			this.fields[key].setValue(data[key] !== undefined ? data[key] : '');
    }
		this.fireEvent("update", this);
	},
	
	getValue: function() {
		var result = {};
		for(var key in this.fields) {
			var field = this.fields[key];
			if(field.isValid() && String(field.getValue()).length > 0) { 
				result[key] = field.getValue();
      }
		}
		
		return result;
	},
  
  onKeyUp: function(event, input, notSure, field) {
    if(event.getKey() == event.ENTER && field.isValid()) {
	    this.hide(true);
	    return;
	  }
	
	  if(field == this.fields.eq) {
	    this.fields.gt.setValue(null);
	    this.fields.lt.setValue(null);
	  } else {
	    this.fields.eq.setValue(null);
	  }
	  
	  this.updateTask.delay(this.updateBuffer);
  }
});
/*!
 * Ext JS Library 3.0.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
/**
 * @class Ext.ux.Spinner
 * @extends Ext.util.Observable
 * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
 */
Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
    incrementValue: 1,
    alternateIncrementValue: 5,
    triggerClass: 'x-form-spinner-trigger',
    splitterClass: 'x-form-spinner-splitter',
    alternateKey: Ext.EventObject.shiftKey,
    defaultValue: 0,
    accelerate: false,

    constructor: function(config){
        Ext.ux.Spinner.superclass.constructor.call(this, config);
        Ext.apply(this, config);
        this.mimicing = false;
    },

    init: function(field){
        this.field = field;

        field.afterMethod('onRender', this.doRender, this);
        field.afterMethod('onEnable', this.doEnable, this);
        field.afterMethod('onDisable', this.doDisable, this);
        field.afterMethod('afterRender', this.doAfterRender, this);
        field.afterMethod('onResize', this.doResize, this);
        field.afterMethod('onFocus', this.doFocus, this);
        field.beforeMethod('onDestroy', this.doDestroy, this);
    },

    doRender: function(ct, position){
        var el = this.el = this.field.getEl();
        var f = this.field;

        if (!f.wrap) {
            f.wrap = this.wrap = el.wrap({
                cls: "x-form-field-wrap"
            });
        }
        else {
            this.wrap = f.wrap.addClass('x-form-field-wrap');
        }

        this.trigger = this.wrap.createChild({
            tag: "img",
            src: Ext.BLANK_IMAGE_URL,
            cls: "x-form-trigger " + this.triggerClass
        });

        if (!f.width) {
            this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
        }

        this.splitter = this.wrap.createChild({
            tag: 'div',
            cls: this.splitterClass,
            style: 'width:13px; height:2px;'
        });
        this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();

        this.proxy = this.trigger.createProxy('', this.splitter, true);
        this.proxy.addClass("x-form-spinner-proxy");
        this.proxy.setStyle('left', '0px');
        this.proxy.setSize(14, 1);
        this.proxy.hide();
        this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {
            dragElId: this.proxy.id
        });

        this.initTrigger();
        this.initSpinner();
    },

    doAfterRender: function(){
        var y;
        if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
            this.el.position();
            this.el.setY(y);
        }
    },

    doEnable: function(){
        if (this.wrap) {
            this.wrap.removeClass(this.field.disabledClass);
        }
    },

    doDisable: function(){
        if (this.wrap) {
            this.wrap.addClass(this.field.disabledClass);
            this.el.removeClass(this.field.disabledClass);
        }
    },

    doResize: function(w, h){
        if (typeof w == 'number') {
            this.el.setWidth(this.field.adjustWidth('input', w - this.trigger.getWidth()));
        }
        this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
    },

    doFocus: function(){
        if (!this.mimicing) {
            this.wrap.addClass('x-trigger-wrap-focus');
            this.mimicing = true;
            Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {
                delay: 10
            });
            this.el.on('keydown', this.checkTab, this);
        }
    },

    // private
    checkTab: function(e){
        if (e.getKey() == e.TAB) {
            this.triggerBlur();
        }
    },

    // private
    mimicBlur: function(e){
        if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
            this.triggerBlur();
        }
    },

    // private
    triggerBlur: function(){
        this.mimicing = false;
        Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
        this.el.un("keydown", this.checkTab, this);
        this.field.beforeBlur();
        this.wrap.removeClass('x-trigger-wrap-focus');
        this.field.onBlur.call(this.field);
    },

    initTrigger: function(){
        this.trigger.addClassOnOver('x-form-trigger-over');
        this.trigger.addClassOnClick('x-form-trigger-click');
    },

    initSpinner: function(){
        this.field.addEvents({
            'spin': true,
            'spinup': true,
            'spindown': true
        });

        this.keyNav = new Ext.KeyNav(this.el, {
            "up": function(e){
                e.preventDefault();
                this.onSpinUp();
            },

            "down": function(e){
                e.preventDefault();
                this.onSpinDown();
            },

            "pageUp": function(e){
                e.preventDefault();
                this.onSpinUpAlternate();
            },

            "pageDown": function(e){
                e.preventDefault();
                this.onSpinDownAlternate();
            },

            scope: this
        });

        this.repeater = new Ext.util.ClickRepeater(this.trigger, {
            accelerate: this.accelerate
        });
        this.field.mon(this.repeater, "click", this.onTriggerClick, this, {
            preventDefault: true
        });

        this.field.mon(this.trigger, {
            mouseover: this.onMouseOver,
            mouseout: this.onMouseOut,
            mousemove: this.onMouseMove,
            mousedown: this.onMouseDown,
            mouseup: this.onMouseUp,
            scope: this,
            preventDefault: true
        });

        this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);

        this.dd.setXConstraint(0, 0, 10)
        this.dd.setYConstraint(1500, 1500, 10);
        this.dd.endDrag = this.endDrag.createDelegate(this);
        this.dd.startDrag = this.startDrag.createDelegate(this);
        this.dd.onDrag = this.onDrag.createDelegate(this);
    },

    onMouseOver: function(){
        if (this.disabled) {
            return;
        }
        var middle = this.getMiddle();
        this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
        this.trigger.addClass(this.tmpHoverClass);
    },

    //private
    onMouseOut: function(){
        this.trigger.removeClass(this.tmpHoverClass);
    },

    //private
    onMouseMove: function(){
        if (this.disabled) {
            return;
        }
        var middle = this.getMiddle();
        if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||
        ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {
        }
    },

    //private
    onMouseDown: function(){
        if (this.disabled) {
            return;
        }
        var middle = this.getMiddle();
        this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
        this.trigger.addClass(this.tmpClickClass);
    },

    //private
    onMouseUp: function(){
        this.trigger.removeClass(this.tmpClickClass);
    },

    //private
    onTriggerClick: function(){
        if (this.disabled || this.el.dom.readOnly) {
            return;
        }
        var middle = this.getMiddle();
        var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
        this['onSpin' + ud]();
    },

    //private
    getMiddle: function(){
        var t = this.trigger.getTop();
        var h = this.trigger.getHeight();
        var middle = t + (h / 2);
        return middle;
    },

    //private
    //checks if control is allowed to spin
    isSpinnable: function(){
        if (this.disabled || this.el.dom.readOnly) {
            Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
            return false;
        }
        return true;
    },

    handleMouseWheel: function(e){
        //disable scrolling when not focused
        if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
            return;
        }

        var delta = e.getWheelDelta();
        if (delta > 0) {
            this.onSpinUp();
            e.stopEvent();
        }
        else
            if (delta < 0) {
                this.onSpinDown();
                e.stopEvent();
            }
    },

    //private
    startDrag: function(){
        this.proxy.show();
        this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
    },

    //private
    endDrag: function(){
        this.proxy.hide();
    },

    //private
    onDrag: function(){
        if (this.disabled) {
            return;
        }
        var y = Ext.fly(this.dd.getDragEl()).getTop();
        var ud = '';

        if (this._previousY > y) {
            ud = 'Up';
        } //up
        if (this._previousY < y) {
            ud = 'Down';
        } //down
        if (ud != '') {
            this['onSpin' + ud]();
        }

        this._previousY = y;
    },

    //private
    onSpinUp: function(){
        if (this.isSpinnable() == false) {
            return;
        }
        if (Ext.EventObject.shiftKey == true) {
            this.onSpinUpAlternate();
            return;
        }
        else {
            this.spin(false, false);
        }
        this.field.fireEvent("spin", this);
        this.field.fireEvent("spinup", this);
    },

    //private
    onSpinDown: function(){
        if (this.isSpinnable() == false) {
            return;
        }
        if (Ext.EventObject.shiftKey == true) {
            this.onSpinDownAlternate();
            return;
        }
        else {
            this.spin(true, false);
        }
        this.field.fireEvent("spin", this);
        this.field.fireEvent("spindown", this);
    },

    //private
    onSpinUpAlternate: function(){
        if (this.isSpinnable() == false) {
            return;
        }
        this.spin(false, true);
        this.field.fireEvent("spin", this);
        this.field.fireEvent("spinup", this);
    },

    //private
    onSpinDownAlternate: function(){
        if (this.isSpinnable() == false) {
            return;
        }
        this.spin(true, true);
        this.field.fireEvent("spin", this);
        this.field.fireEvent("spindown", this);
    },

    spin: function(down, alternate){
        var v = parseFloat(this.field.getValue());
        var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
        (down == true) ? v -= incr : v += incr;

        v = (isNaN(v)) ? this.defaultValue : v;
        v = this.fixBoundries(v);
        this.field.setRawValue(v);
    },

    fixBoundries: function(value){
        var v = value;

        if (this.field.minValue != undefined && v < this.field.minValue) {
            v = this.field.minValue;
        }
        if (this.field.maxValue != undefined && v > this.field.maxValue) {
            v = this.field.maxValue;
        }

        return this.fixPrecision(v);
    },

    // private
    fixPrecision: function(value){
        var nan = isNaN(value);
        if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {
            return nan ? '' : value;
        }
        return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));
    },

    doDestroy: function(){
        if (this.trigger) {
            this.trigger.remove();
        }
        if (this.wrap) {
            this.wrap.remove();
            delete this.field.wrap;
        }

        if (this.splitter) {
            this.splitter.remove();
        }

        if (this.dd) {
            this.dd.unreg();
            this.dd = null;
        }

        if (this.proxy) {
            this.proxy.remove();
        }

        if (this.repeater) {
            this.repeater.purgeListeners();
        }
    }
});

//backwards compat
Ext.form.Spinner = Ext.ux.Spinner;/*!
 * Ext JS Library 3.0.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.form');

/**
 * @class Ext.ux.form.SpinnerField
 * @extends Ext.form.NumberField
 * Creates a field utilizing Ext.ux.Spinner
 * @xtype spinnerfield
 */
Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
    deferHeight: true,
    autoSize: Ext.emptyFn,
    onBlur: Ext.emptyFn,
    adjustSize: Ext.BoxComponent.prototype.adjustSize,

	constructor: function(config) {
		var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');

		var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);

		var plugins = config.plugins
			? (Ext.isArray(config.plugins)
				? config.plugins.push(spl)
				: [config.plugins, spl])
			: spl;

		Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));
	},

    onShow: function(){
        if (this.wrap) {
            this.wrap.dom.style.display = '';
            this.wrap.dom.style.visibility = 'visible';
        }
    },

    onHide: function(){
        this.wrap.dom.style.display = 'none';
    },

    // private
    getResizeEl: function(){
        return this.wrap;
    },

    // private
    getPositionEl: function(){
        return this.wrap;
    },

    // private
    alignErrorIcon: function(){
        if (this.wrap) {
            this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
        }
    },

    validateBlur: function(){
        return true;
    }
});

Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);

//backwards compat
Ext.form.SpinnerField = Ext.ux.form.SpinnerField;

/*
 * MultiSelectTreePanel v 1.0 (Initial release)
 *
 * This work is derivative of Ext-JS 2.2. Much of the code is modified versions of default code.
 * Refer to Ext-JS 2.2 licencing for more information. http://extjs.com/license
 *
 * Any and all original code is made available as is for whatever purpose you see fit.
 *
 * Should be a largely drop in replacement for ordinary TreePanel when you require multiselect
 * with drag and drop. Overrides most of the methods and events to pass a nodelist rather than
 * a single node.
 *
 * Note that the code is provided as-is and should be considered experimental and likely to contain
 * bugs, especially when combined with other extensions or modifications to the default library.
 *
 * It has been tested against Ext-JS 2.2 and 2.2.1 with:
 *
 * Firefox 3, Opera 9.5, Safari 3.1, MSIE 6 & 7.
 *
 * Usage:
 *
 * Add the following CSS to make the floating "drag" version of the tree indent prettily..

.x-dd-drag-ghost .x-tree-node-indent,.x-dd-drag-ghost .x-tree-ec-icon {display: inline !important;}

 *
 * If you are using Ext-JS 2.2.1 or earlier you need to add this override! (reported as a bug)
 
Ext.override(Ext.tree.TreeDropZone, {
    completeDrop : function(de){
        var ns = de.dropNode, p = de.point, t = de.target;
        if(!Ext.isArray(ns)){
            ns = [ns];
        }
        var n, node, ins = false;
        if (p != 'append'){
            ins = true;
            node = (p == 'above') ? t : t.nextSibling;
        }
        for(var i = 0, len = ns.length; i < len; i++){
            n = ns[i];
            if (ins){
                t.parentNode.insertBefore(n, node);
            }else{
                t.appendChild(n);
            }
	        if(Ext.enableFx && this.tree.hlDrop){
	           n.ui.highlight();
	        }
        }
	    ns[0].ui.focus();
        t.ui.endDrop();
        this.tree.fireEvent("nodedrop", de);
    }
    
}); 
 
 *
 * Instantiate like a normal tree (except DD stuff is enabled by default)
 
    var tree = new Ext.ux.MultiSelectTreePanel({
        autoScroll:true,
        width:400,
        height:500,
        animate:true,
        containerScroll: true,
        root: new Ext.tree.AsyncTreeNode({
        	text: 'A Book',
        	draggable:false,
        	id:'node0'
    	}),
        loader: new Ext.tree.TreeLoader({
            dataUrl:'bookdata.json'
        })
    });
 	tree.render("target");

 *
 * When listening for DND events look for dragdata.nodes instead of dragdata.node
 *
 * Use ctrl-click to select multiple nodes.
 * Use shift-click to select a range of nodes.
 *
 * Enjoy
 */

Ext.ux.FixedMultiSelectionModel = Ext.extend(Ext.tree.MultiSelectionModel, {
	// disabled tracking of mouse clicks because it doubles up drag selection...
	onNodeClick : function(node, e){
		if (e.shiftKey) e.preventDefault();
		// this.select(node);
	},

	// private
	sortSelNodes: function() {
		if (this.selNodes.length > 0) {
			if (!this.selNodes[0].ui.elNode)
			// sort nodes into document order.. (taken from quirksmode)
			if (this.selNodes[0].ui.elNode.sourceIndex) {
				// IE source index method
				this.selNodes.sort(function (a,b) {
					return a.ui.elNode.sourceIndex - b.ui.elNode.sourceIndex;
				});
			} else if (this.selNodes[0].ui.elNode.compareDocumentPosition) {
				// W3C DOM lvl 3 method (Gecko)
				this.selNodes.sort(function (a,b) {
					return 3 - (a.ui.elNode.compareDocumentPosition(b.ui.elNode) & 6);
				});
			}
		}
	},

	// overwritten from MultiSelectionModel to fix unselecting...
	select : function(node, e, keepExisting){
		// Add in setting an array as selected... (for multi-selecting D&D nodes)
		if(node instanceof Array){
			for (var c=0;c<node.length;c++) {
				this.selMap[node[c].id] = node[c];
				this.selNodes.push(node[c]);
				node[c].ui.onSelectedChange(true);
			}
			this.sortSelNodes();
			this.fireEvent("selectionchange", this, this.selNodes, this.lastSelNode);
			return node;
		}
		// Shift Select to select a range
		// NOTE: Doesn't change lastSelNode
		// EEK has to be a prettier way to do this
		if (e && e.shiftKey && this.selNodes.length > 0) {
			this.lastSelNode = this.lastSelNode || this.selNodes[0];
			var before = false;
			if (this.lastSelNode == node) {
				// check dom node ordering (from ppk of quirksmode.org)
			} else if (node.ui.elNode.sourceIndex) {
				// IE source index method
				before = (this.lastSelNode.ui.elNode.sourceIndex - node.ui.elNode.sourceIndex) > 0;
			} else if (node.ui.elNode.compareDocumentPosition) {
				// W3C DOM lvl 3 method (Gecko)
				var rel = this.lastSelNode.ui.elNode.compareDocumentPosition(node.ui.elNode);
				before = !!(rel & 2);
			} else {
				// Safari doesn't support compareDocumentPosition or sourceIndex
				// from http://code.google.com/p/doctype/wiki/ArticleNodeCompareDocumentOrder

				var range1 = document.createRange();
				range1.selectNode(this.lastSelNode.ui.elNode);
				range1.collapse(true);

				var range2 = document.createRange();
				range2.selectNode(node.ui.elNode);
				range2.collapse(true);

				before = range1.compareBoundaryPoints(Range.START_TO_END, range2) > 0;
			}
			this.clearSelections(true);
			var cont = true;
			var inside = false;
			var parent = this.lastSelNode;
			// ummm... yeah don't read this bit...
			do {
				for (var next=parent;next!=null;next=(before?next.previousSibling:next.nextSibling)) {
					// hack to make cascade work the way I want it to
					inside = inside || (before && (next == node || next.contains(node)));
					if (next.isExpanded()) {
						next.cascade(function(n) {
							if (cont != inside) {
								this.selNodes.push(n);
								this.selMap[n.id] = n;
								n.ui.onSelectedChange(true);
							}
							cont = (cont && n != node);
							return true;
						}, this);
					} else {
						this.selNodes.push(next);
						this.selMap[next.id] = next;
						next.ui.onSelectedChange(true);
						cont = (next != node);
					}
					if (!cont) break;
				}
				if (!cont) break;
				while ((parent = parent.parentNode) != null) {
					if (before) {
						this.selNodes.push(parent);
						this.selMap[parent.id] = parent;
						parent.ui.onSelectedChange(true);
					}
					cont = (cont && parent != node);
					if (before && parent.previousSibling) {
						parent = parent.previousSibling;
						break;
					}
					if (!before && parent.nextSibling) {
						parent = parent.nextSibling;
						break;
					}
				}
				if (!cont) break;
			} while (parent != null);
			if (!node.isSelected()) {
				this.selNodes.push(node);
				this.selMap[node.id] = node;
				node.ui.onSelectedChange(true);
			}
			this.sortSelNodes();
			this.fireEvent("selectionchange", this, this.selNodes, node);
			e.preventDefault();
			return node;
		} else if(keepExisting !== true) {
			this.clearSelections(true);
		}
		if(this.isSelected(node)) {
			// handle deselect of node...
			if (keepExisting === true) {
				this.unselect(node);
				if (this.lastSelNode === node) {
					this.lastSelNode = this.selNodes[0];
				}
				return node;
			}
			this.lastSelNode = node;
			return node;
		}
		// save a resort later on...
		this.selNodes.push(node);
		this.selMap[node.id] = node;
		node.ui.onSelectedChange(true);
		this.sortSelNodes();
		this.lastSelNode = node;
		this.fireEvent("selectionchange", this, this.selNodes, this.lastSelNode);
		return node;
	},
	// returns selected nodes precluding children of other selected nodes...
	// used for multi drag and drop...
	getUniqueSelectedNodes: function() {
		var ret = [];
		for (var c=0;c<this.selNodes.length;c++) {
			var parent = this.selNodes[c];
			ret.push(parent);
			// nodes are sorted(?) so skip over subsequent nodes inside this one..
			while ((c+1)<this.selNodes.length && parent.contains(this.selNodes[c+1])) c++;
		}
		return ret;
	}
});
/*
	Enhanced to support dragging multiple nodes...
	
	for extension refer to data.nodes instead of data.node
	
*/
Ext.ux.MultiSelectTreeDragZone = Ext.extend(Ext.tree.TreeDragZone, {
	onBeforeDrag : function(data, e){
		if (data.nodes && data.nodes.length > 0) {
			for (var c=0;c<data.nodes.length;c++) {
				n = data.nodes[c];
				if (n.draggable === false || n.disabled) return false
			}
			return true;
		}
		return false;
		
	},
	// what a mess!!!
	// fixed to handle multiSelectionModel, however the result is very hacky
	getDragData : function(e) {
		// use tree selection model..
		var selModel = this.tree.getSelectionModel();
		// get event target
		var target = Ext.dd.Registry.getHandleFromEvent(e);
		// if no target (die)
		if (target == null) return;
		if (target.node.isSelected() && e.ctrlKey) {
			selModel.unselect(target.node);
			return;
		}
		var selNodes = [];
		if (!selModel.getSelectedNodes) {
			// if not multiSelectionModel.. just use the target...
			selNodes = [target.node];
		} else {
			// if target not selected select it...
			if (!target.node.isSelected() || e.shiftKey) {
				selModel.select(target.node, e, e.ctrlKey);
			}
			// get selected nodes - nested nodes...
			selNodes = selModel.getUniqueSelectedNodes();
		}
		// if no nodes selected stop now...
		if (!selNodes || selNodes.length < 1) return;
		var dragData = { nodes: selNodes };
		// create a container for the proxy...
		var div = document.createElement('ul'); // create the multi element drag "ghost"
		// add classes to keep is pretty...
		div.className = 'x-tree-node-ct x-tree-lines';
		// add actual dom nodes to div (instead of tree nodes)
		//var height = 0;
		for(var i = 0, len = selNodes.length; i < len; i++) {
			// height += Ext.fly(selNodes[i].ui.elNode.parentNode).getHeight();
			// add entire node to proxy
			div.appendChild(selNodes[i].ui.elNode.parentNode.cloneNode(true));
			// limit proxy height to around 150px (need setting for this really)
			// removed because the height varies so much anyways...
			//if (height>150 && (i+1)<selNodes.length) {
			//	var elipsis = document.createElement("div");
			//	elipsis.innerHTML = "<b>...</b>";
			//	div.appendChild(elipsis);
			//	break;
			//}
		}
		// fix extra indenting by removing extra spacers
		// should really modify UI rendering code to render a duplicate subtree but this is simpler...
		// no idea if this really gets all nodes or not...
		var nodes = Ext.query(".x-tree-node-el", div);
		for (var c=0;c<nodes.length;c++) {
			// remove highlighting...
			Ext.fly(nodes[c]).removeClass(['x-tree-selected','x-tree-node-over']);
			// start at 1 to leave in folder/user icon
			var depth = 1;
			// calculate indenting required in proxy
			for (var node=nodes[c].parentNode.parentNode;node!=null && node.parentNode!=null;node=node.parentNode.parentNode) {
				depth++;
			}
			var spacers = Ext.query("img", nodes[c]);
			for (var r=0;r<spacers.length&&r<spacers.length-depth;r++) {
				spacers[r].parentNode.removeChild(spacers[r]);
			}
		}
		dragData.ddel = div;
		return dragData;
	},
	// fix from TreeDragZone (references dragData.node instead of dragData.nodes)
	onInitDrag : function(e){
		var data = this.dragData;
		this.tree.eventModel.disable();
		this.proxy.update("");
		this.proxy.ghost.dom.appendChild(data.ddel);
		this.tree.fireEvent("startdrag", this.tree, data.nodes, e);
	},
	// Called from TreeDropZone (looks like hack for handling multiple tree nodes)
	getTreeNode: function() {
		return this.dragData.nodes;
	},
	// fix from TreeDragZone (refers to data.node instead of data.nodes)
	// Don't know what this does, so leaving as first node.
	getRepairXY : function(e, data){
		return data.nodes[0].ui.getDDRepairXY();
	},

	// fix from TreeDragZone (refers to data.node instead of data.nodes)
	onEndDrag : function(data, e){
		this.tree.eventModel.enable.defer(100, this.tree.eventModel);
		this.tree.fireEvent("enddrag", this.tree, data.nodes, e);
	},

	// fix from TreeDragZone (refers to dragData.node instead of dragData.nodes)
	onValidDrop : function(dd, e, id){
		this.tree.fireEvent("dragdrop", this.tree, this.dragData.nodes, dd, e);
		this.hideProxy();
	},

	// fix for invalid Drop
	beforeInvalidDrop : function(e, id){
		// this scrolls the original position back into view
		var sm = this.tree.getSelectionModel();
		sm.clearSelections();
		sm.select(this.dragData.nodes, e, true);
	}

});

/*

MultiSelectTreeDropZone

Contains following fixups

- modified functions to handle multiple nodes in dd operation
	isValidDropPoint
	afterRepair
- modified getDropPoint such that isValidDropPoint can simulate leaf style below inserting.
	Overriding isValidDropPoint affects getDropPoint affects onNodeOver and onNodeDrop

Refer to data.nodes instead of data.node for events..

*/
Ext.ux.MultiSelectTreeDropZone = Ext.extend(Ext.tree.TreeDropZone, {

	// fix from TreeDropZone (referred to data.node instead of data.nodes)
	isValidDropPoint : function(n, pt, dd, e, data){
		if(!n || !data) { return false; }
		var targetNode = n.node;
		var dropNodes = data.nodes?data.nodes:[data.node];
		// default drop rules
		if(!(targetNode && targetNode.isTarget && pt)){
			return false;
		}
		if(pt == "append" && targetNode.allowChildren === false){
			return false;
		}
		if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
			return false;
		}
		// don't allow dropping a treenode inside itself...
		for (var c=0;c<dropNodes.length;c++) {
			if(dropNodes[c] && (targetNode == dropNodes[c] || dropNodes[c].contains(targetNode))){
				return false;
			}
		}
		// reuse the object
		var overEvent = this.dragOverData;
		overEvent.tree = this.tree;
		overEvent.target = targetNode;
		overEvent.data = data;
		overEvent.point = pt;
		overEvent.source = dd;
		overEvent.rawEvent = e;
		overEvent.dropNode = dropNodes;
		overEvent.cancel = false;
		var result = this.tree.fireEvent("nodedragover", overEvent);
		return overEvent.cancel === false && result !== false;
	},

	// override to allow insert "below" when leaf != true...
	getDropPoint : function(e, n, dd, data){
		var tn = n.node;
		if(tn.isRoot){
			return this.isValidDropPoint(n, "append", dd, e, data)? "append" : false;
		}
		var dragEl = n.ddel;
		var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
		var y = Ext.lib.Event.getPageY(e);
		var noAppend = tn.allowChildren === false || tn.isLeaf() || !this.isValidDropPoint(n, "append", dd, e, data);
		if(!this.appendOnly && tn.parentNode.allowChildren !== false){
			var noBelow = false;
			if(!this.allowParentInsert){
				noBelow = tn.hasChildNodes() && tn.isExpanded();
			}
			var q = (b - t) / (noAppend ? 2 : 3);
			if(y >= t && y < (t + q) && this.isValidDropPoint(n, "above", dd, e, data)){
				return "above";
			}else if(!noBelow && (noAppend || y >= b-q && y <= b) && this.isValidDropPoint(n, "below", dd, e, data)){
				return "below";
			}
		}
		return noAppend? false: "append";
	},

	// Override because it calls getDropPoint and isValidDropPoint
	onNodeOver : function(n, dd, e, data){
		var pt = this.getDropPoint(e, n, dd, data);
		var node = n.node;


		if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
			this.queueExpand(node);
		}else if(pt != "append"){
			this.cancelExpand();
		}

		var returnCls = this.dropNotAllowed;
		if(pt){
			var el = n.ddel;
			var cls;
			if(pt == "above"){
				returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
				cls = "x-tree-drag-insert-above";
			}else if(pt == "below"){
				returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
				cls = "x-tree-drag-insert-below";
			}else{
				returnCls = "x-tree-drop-ok-append";
				cls = "x-tree-drag-append";
			}
			if(this.lastInsertClass != cls){
				Ext.fly(el).replaceClass(this.lastInsertClass, cls);
				this.lastInsertClass = cls;
			}
		}
		return returnCls;
	},

	// Override because it calls getDropPoint and isValidDropPoint
	onNodeDrop : function(n, dd, e, data){
		var point = this.getDropPoint(e, n, dd, data);
		var targetNode = n.node;
		targetNode.ui.startDrop();
		if(point === false) {
			targetNode.ui.endDrop();
			return false;
		}

		var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
		var dropEvent = {
			tree : this.tree,
			target: targetNode,
			data: data,
			point: point,
			source: dd,
			rawEvent: e,
			dropNode: dropNode,
			cancel: !dropNode,
			dropStatus: false
		};
		var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
		if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
			targetNode.ui.endDrop();
			return dropEvent.dropStatus;
		}

		targetNode = dropEvent.target;
		if(point == "append" && !targetNode.isExpanded()){
			targetNode.expand(false, null, function(){
				this.completeDrop(dropEvent);
			}.createDelegate(this));
		}else{
			this.completeDrop(dropEvent);
		}
		return true;
	},

	// fix from TreeDropZone (referred to data.node instead of data.nodes)
	afterRepair : function(data){
		if(data && Ext.enableFx){
			for (var c=0;c<data.nodes.length;c++) {
				data.nodes[c].ui.highlight();
			}
		}
		this.hideProxy();
	}

});

/*

	MultiSelectTreePanel

	sets up using FixedMultiSelectionModel
	and initing with extended DragZone and DropZone by default

*/

Ext.ux.MultiSelectTreePanel = Ext.extend(Ext.tree.TreePanel, {
	enableDD: true,

	getSelectionModel : function(){
		if(!this.selModel){
			this.selModel = new Ext.ux.FixedMultiSelectionModel();
		}
		return this.selModel;
	},

	initEvents: function() {
		this.dragZone = new Ext.ux.MultiSelectTreeDragZone(this, {
								ddGroup: this.ddGroup || "TreeDD",
								scroll: this.ddScroll
							});
		this.dropZone = new Ext.ux.MultiSelectTreeDropZone(this, this.dropConfig || {
								ddGroup: this.ddGroup || "TreeDD",
								appendOnly: this.ddAppendOnly === true
							});
		Ext.ux.MultiSelectTreePanel.superclass.initEvents.apply(this, arguments);

	}
});

Ext.reg('multiselecttreepanel', Ext.ux.MultiSelectTreePanel);




// this code goes in a javascript include file somewhere
Ext.namespace('Ext.ux.dd');

Ext.ux.dd.GridReorderDropTarget = function(grid, config) {

this.target = new Ext.dd.DropTarget(grid.getEl(), {
    ddGroup: grid.ddGroup || 'GridDD'
    ,grid: grid
    ,gridDropTarget: this
    ,notifyDrop: function(dd, e, data) {

        // Remove drag lines. The If condition prevents null error when
        // drop occurs without dragging out of the selection area.
        if (this.currentRowEl) {
            this.currentRowEl.removeClass("grid-row-insert-below");
            this.currentRowEl.removeClass("grid-row-insert-above");
        }

        // determine the row
        var t = Ext.lib.Event.getTarget(e);
        var rindex = this.grid.getView().findRowIndex(t);

        if (rindex === false) return false;
        if (rindex == data.rowIndex) return false;

        // fire the before move/copy event
        if (this.gridDropTarget.fireEvent(this.copy?'beforerowcopy':'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections, 123) === false) return false;

        // update the store
        var ds = this.grid.getStore();

        // Changes for multiselction by Spirit
        var selections = new Array();
        var keys = ds.data.keys;
        for (key in keys) {
            for(i = 0; i < data.selections.length; i++) {
                if (keys[key]==data.selections[i].id) {
                    // Exit to prevent drop of selected records on itself.
                    if (rindex == key) return false;
                    selections.push(data.selections[i]);
                }
            }
        }

        if (!this.copy) {
            for(i = 0; i < data.selections.length; i++) {
                ds.remove(ds.getById(data.selections[i].id));
            }
        }

        if (rindex > data.rowIndex && data.selections.length > 1) {
            rindex = rindex - (data.selections.length - 1);
        }

        for(i = selections.length-1; i>=0; i--) {
            var insertIndex = rindex;
            // Logic (convoluted) depending on if rows were moved up or down.
            if (rindex > data.rowIndex && this.rowPosition < 0) insertIndex--;
            if (rindex < data.rowIndex && this.rowPosition > 0) insertIndex++;
            ds.insert(insertIndex, selections[i]);
        }

        // re-select the row(s)
        sm = this.grid.getSelectionModel();
        if (sm) sm.selectRecords(data.selections);

        // fire the after move/copy event
        this.gridDropTarget.fireEvent(this.copy?'afterrowcopy':'afterrowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections);

        return true;
    }
    ,notifyOver: function(dd, e, data) {

        var t = Ext.lib.Event.getTarget(e);
        var rindex = this.grid.getView().findRowIndex(t);

        // Similar to the code in notifyDrop. Filters for selected rows and
        // quits function if any one row matches the current selected row.
        var ds = this.grid.getStore();
        var keys = ds.data.keys;
        for (key in keys) {
            for(i = 0; i < data.selections.length; i++) {
                if (keys[key]==data.selections[i].id) {
                    if (rindex == key) {
                        if (this.currentRowEl) {
                            this.currentRowEl.removeClass("grid-row-insert-below");
                            this.currentRowEl.removeClass("grid-row-insert-above");
                        }
                        return this.dropNotAllowed;
                    }
                }
            }
        }

        // If on first row, remove upper line. Prevents negative
        // index error as a result of rindex going negative.
        if (rindex < 0 || rindex === false) {
            this.currentRowEl.removeClass("grid-row-insert-above");
            return this.dropNotAllowed;
        }

        try {
            var currentRow = this.grid.getView().getRow(rindex);
            // Find position of row relative to page (adjusting for grid's scroll position)
            var resolvedRow = new Ext.Element(currentRow).getY() - this.grid.getView().scroller.dom.scrollTop;
            var rowHeight = currentRow.offsetHeight;

            // Cursor relative to a row. -ve value implies cursor is above the
            // row's middle and +ve value implues cursor is below the row's middle.
            this.rowPosition = e.getPageY() - resolvedRow - (rowHeight/2);

            // Clear drag line.
            if (this.currentRowEl) {
                this.currentRowEl.removeClass("grid-row-insert-below");
                this.currentRowEl.removeClass("grid-row-insert-above");
            }

            if (this.rowPosition > 0) {
                // If the pointer is on the bottom half of the row.
                this.currentRowEl = new Ext.Element(currentRow);
                this.currentRowEl.addClass("grid-row-insert-below");
            } else {
                // If the pointer is on the top half of the row.
                if (rindex-1 >= 0) {
                    var previousRow = this.grid.getView().getRow(rindex-1);
                    this.currentRowEl = new Ext.Element(previousRow);
                    this.currentRowEl.addClass("grid-row-insert-below");
                } else {
                    // If the pointer is on the top half of the first row.
                    this.currentRowEl.addClass("grid-row-insert-above");
                }
            }
        } catch (err) {
            
            rindex = false;
        }

        return (rindex === false)? this.dropNotAllowed : this.dropAllowed;
    }
    ,notifyOut: function(dd, e, data) {
        // Remove drag lines when pointer leaves the gridView.
        if (this.currentRowEl) {
            this.currentRowEl.removeClass("grid-row-insert-above");
            this.currentRowEl.removeClass("grid-row-insert-below");
        }
    }
});

if (config) {
    Ext.apply(this.target, config);
    if (config.listeners) Ext.apply(this,{listeners: config.listeners});
}

this.addEvents({
    "beforerowmove": true
    ,"afterrowmove": true
    ,"beforerowcopy": true
    ,"afterrowcopy": true
}); 
    Ext.ux.dd.GridReorderDropTarget.superclass.constructor.call(this);
};


Ext.extend(Ext.ux.dd.GridReorderDropTarget, Ext.util.Observable, {
    getTarget: function() {
        return this.target;
    }
    ,getGrid: function() {
        return this.target.grid;
    }
    ,getCopy: function() {
        return this.target.copy?true:false;
    }
    ,setCopy: function(b) {
        this.target.copy = b?true:false;
    }
}); 

/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.grid.GridFilters = function(config){		
	this.filters = new Ext.util.MixedCollection();
	this.filters.getKey = function(o) {return o ? o.dataIndex : null};
	
	for(var i=0, len=config.filters.length; i<len; i++) {
		this.addFilter(config.filters[i]);
  }
  
	this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);
	
	delete config.filters;
	Ext.apply(this, config);
};
Ext.extend(Ext.grid.GridFilters, Ext.util.Observable, {
	/**
	 * @cfg {Integer} updateBuffer
	 * Number of milisecond to defer store updates since the last filter change.
	 */
	updateBuffer: 500,
	/**
	 * @cfg {String} paramPrefix
	 * The url parameter prefix for the filters.
	 */
	paramPrefix: 'filter',
	/**
	 * @cfg {String} fitlerCls
	 * The css class to be applied to column headers that active filters. Defaults to 'ux-filterd-column'
	 */
	filterCls: 'ux-filtered-column',
	/**
	 * @cfg {Boolean} local
	 * True to use Ext.data.Store filter functions instead of server side filtering.
	 */
	local: false,
	/**
	 * @cfg {Boolean} autoReload
	 * True to automagicly reload the datasource when a filter change happens.
	 */
	autoReload: true,
	/**
	 * @cfg {String} stateId
	 * Name of the Ext.data.Store value to be used to store state information.
	 */
	stateId: undefined,
	/**
	 * @cfg {Boolean} showMenu
	 * True to show the filter menus
	 */
	showMenu: true,
    /**
     * @cfg {String} filtersText
     * The text displayed for the "Filters" menu item
     */
    filtersText: 'Filters',

	init: function(grid){
    if(grid instanceof Ext.grid.GridPanel){
      this.grid  = grid;
      
      this.store = this.grid.getStore();
      if(this.local){
        this.store.on('load', function(store) {
          store.filterBy(this.getRecordFilter());
        }, this);
      } else {
        this.store.on('beforeload', this.onBeforeLoad, this);
      }
      
      this.grid.filters = this;
      
      this.grid.addEvents('filterupdate');
      
      grid.on("render", this.onRender, this);
      grid.on("beforestaterestore", this.applyState, this);
      grid.on("beforestatesave", this.saveState, this);
      
    } else if(grid instanceof Ext.PagingToolbar) {
      this.toolbar = grid;
    }
	},
		
	/** private **/
	applyState: function(grid, state) {
		this.suspendStateStore = true;
		this.clearFilters();
		if(state.filters) {
			for(var key in state.filters) {
				var filter = this.filters.get(key);
				if(filter) {
					filter.setValue(state.filters[key]);
					filter.setActive(true);
				}
			}
    }
    
		this.deferredUpdate.cancel();
		if(this.local) {
			this.reload();
    }
    
		this.suspendStateStore = false;
	},
	
	/** private **/
	saveState: function(grid, state){
		var filters = {};
		this.filters.each(function(filter) {
			if(filter.active) {
				filters[filter.dataIndex] = filter.getValue();
      }
		});
		return state.filters = filters;
	},
	
	/** private **/
	onRender: function(){
		var hmenu;
		
		if(this.showMenu) {
			hmenu = this.grid.getView().hmenu;
			
			this.sep  = hmenu.addSeparator();
			this.menu = hmenu.add(new Ext.menu.CheckItem({
					text: this.filtersText,
					menu: new Ext.menu.Menu()
				}));
			this.menu.on('checkchange', this.onCheckChange, this);
			this.menu.on('beforecheckchange', this.onBeforeCheck, this);
				
			hmenu.on('beforeshow', this.onMenu, this);
		}
		
		this.grid.getView().on("refresh", this.onRefresh, this);
		this.updateColumnHeadings(this.grid.getView());
	},
	
	/** private **/
	onMenu: function(filterMenu) {
		var filter = this.getMenuFilter();
		if(filter) {
			this.menu.menu = filter.menu;
			this.menu.setChecked(filter.active, false);
		}
		
		this.menu.setVisible(filter !== undefined);
		this.sep.setVisible(filter !== undefined);
	},
	
	/** private **/
	onCheckChange: function(item, value) {
		this.getMenuFilter().setActive(value);
	},
	
	/** private **/
	onBeforeCheck: function(check, value) {
		return !value || this.getMenuFilter().isActivatable();
	},
	
	/** private **/
	onStateChange: function(event, filter) {
    if(event == "serialize") {
      return;
    }
    
		if(filter == this.getMenuFilter()) {
			this.menu.setChecked(filter.active, false);
    }
			
		if(this.autoReload || this.local) {
			this.deferredUpdate.delay(this.updateBuffer);
    }
		
		var view = this.grid.getView();
		this.updateColumnHeadings(view);
			
		this.grid.saveState();
			
		this.grid.fireEvent('filterupdate', this, filter);
	},
	
	/** private **/
	onBeforeLoad: function(store, options) {
    options.params = options.params || {};
		this.cleanParams(options.params);		
		var params = this.buildQuery(this.getFilterData());
		Ext.apply(options.params, params);
	},
	
	/** private **/
	onRefresh: function(view) {
		this.updateColumnHeadings(view);
	},
	
	/** private **/
	getMenuFilter: function() {
		var view = this.grid.getView();
		if(!view || view.hdCtxIndex === undefined) {
			return null;
    }
		
		return this.filters.get(view.cm.config[view.hdCtxIndex].dataIndex);
	},
	
	/** private **/
	updateColumnHeadings: function(view) {
		if(!view || !view.mainHd) {
      return;
    }
		
		var hds = view.mainHd.select('td').removeClass(this.filterCls);
		for(var i=0, len=view.cm.config.length; i<len; i++) {
			var filter = this.getFilter(view.cm.config[i].dataIndex);
			if(filter && filter.active) {
				hds.item(i).addClass(this.filterCls);
      }
		}
	},
	
	/** private **/
	reload: function() {
		if(this.local){
			this.grid.store.clearFilter(true);
			this.grid.store.filterBy(this.getRecordFilter());
		} else {
			this.deferredUpdate.cancel();
			var store = this.grid.store;
			if(this.toolbar) {
				var start = this.toolbar.paramNames.start;
				if(store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {
					store.lastOptions.params[start] = 0;
        }
			}
			store.reload();
		}
	},
	
	/**
	 * Method factory that generates a record validator for the filters active at the time
	 * of invokation.
	 * 
	 * @private
	 */
	getRecordFilter: function() {
		var f = [];
		this.filters.each(function(filter) {
			if(filter.active) {
        f.push(filter);
      }
		});
		
		var len = f.length;
		return function(record) {
			for(var i=0; i<len; i++) {
				if(!f[i].validateRecord(record)) {
					return false;
        }
      }
			return true;
		};
	},
	
	/**
	 * Adds a filter to the collection.
	 * 
	 * @param {Object/Ext.grid.filter.Filter} config A filter configuration or a filter object.
	 * 
	 * @return {Ext.grid.filter.Filter} The existing or newly created filter object.
	 */
	addFilter: function(config) {
		var filter = config.menu ? config : new (this.getFilterClass(config.type))(config);
		this.filters.add(filter);
		
		Ext.util.Observable.capture(filter, this.onStateChange, this);
		return filter;
	},
	
	/**
	 * Returns a filter for the given dataIndex, if on exists.
	 * 
	 * @param {String} dataIndex The dataIndex of the desired filter object.
	 * 
	 * @return {Ext.grid.filter.Filter}
	 */
	getFilter: function(dataIndex){
		return this.filters.get(dataIndex);
	},

	/**
	 * Turns all filters off. This does not clear the configuration information.
	 */
	clearFilters: function() {
		this.filters.each(function(filter) {
			filter.setActive(false);
		});
	},

	/** private **/
	getFilterData: function() {
		var filters = [];
		
		this.filters.each(function(f) {
			if(f.active) {
				var d = [].concat(f.serialize());
				for(var i=0, len=d.length; i<len; i++) {
					filters.push({field: f.dataIndex, data: d[i]});
        }
			}
		});
		
		return filters;
	},
	
	/**
	 * Function to take structured filter data and 'flatten' it into query parameteres. The default function
	 * will produce a query string of the form:
	 * 		filters[0][field]=dataIndex&filters[0][data][param1]=param&filters[0][data][param2]=param...
	 * 
	 * @param {Array} filters A collection of objects representing active filters and their configuration.
	 * 	  Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured
	 *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.
	 * 
	 * @return {Object} Query keys and values
	 */
	buildQuery: function(filters) {
		var p = {};
		for(var i=0, len=filters.length; i<len; i++) {
			var f = filters[i];
			var root = [this.paramPrefix, '[', i, ']'].join('');
			p[root + '[field]'] = f.field;
			
			var dataPrefix = root + '[data]';
			for(var key in f.data) {
				p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];
      }
		}
		
		return p;
	},
	
	/**
	 * Removes filter related query parameters from the provided object.
	 * 
	 * @param {Object} p Query parameters that may contain filter related fields.
	 */
	cleanParams: function(p) {
		var regex = new RegExp("^" + this.paramPrefix + "\[[0-9]+\]");
		for(var key in p) {
			if(regex.test(key)) {
				delete p[key];
      }
    }
	},
	
	/**
	 * Function for locating filter classes, overwrite this with your favorite
	 * loader to provide dynamic filter loading.
	 * 
	 * @param {String} type The type of filter to load.
	 * 
	 * @return {Class}
	 */
	getFilterClass: function(type){
		return Ext.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];
	}
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.ns("Ext.grid.filter");
Ext.grid.filter.Filter = function(config){
	Ext.apply(this, config);
		
	this.events = {
		/**
		 * @event activate
		 * Fires when a inactive filter becomes active
		 * @param {Ext.ux.grid.filter.Filter} this
		 */
		'activate': true,
		/**
		 * @event deactivate
		 * Fires when a active filter becomes inactive
		 * @param {Ext.ux.grid.filter.Filter} this
		 */
		'deactivate': true,
		/**
		 * @event update
		 * Fires when a filter configuration has changed
		 * @param {Ext.ux.grid.filter.Filter} this
		 */
		'update': true,
		/**
		 * @event serialize
		 * Fires after the serialization process. Use this to apply additional parameters to the serialized data.
		 * @param {Array/Object} data A map or collection of maps representing the current filter configuration.
		 * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.
		 **/
		'serialize': true
	};
	Ext.grid.filter.Filter.superclass.constructor.call(this);
	
	this.menu = new Ext.menu.Menu();
	this.init();
	
	if(config && config.value) {
		this.setValue(config.value);
		this.setActive(config.active !== false, true);
		delete config.value;
	}
};
Ext.extend(Ext.grid.filter.Filter, Ext.util.Observable, {
	/**
	 * @cfg {Boolean} active
	 * Indicates the default status of the filter (defaults to false).
	 */
    /**
     * True if this filter is active. Read-only.
     * @type Boolean
     * @property
     */
	active: false,
	/**
	 * @cfg {String} dataIndex 
	 * The {@link Ext.data.Store} data index of the field this filter represents. The dataIndex does not actually
	 * have to exist in the store.
	 */
	dataIndex: null,
	/**
	 * The filter configuration menu that will be installed into the filter submenu of a column menu.
	 * @type Ext.menu.Menu
	 * @property
	 */
	menu: null,
	
	/**
	 * Initialize the filter and install required menu items.
	 */
	init: Ext.emptyFn,
	
	fireUpdate: function() {
		this.value = this.item.getValue();
		
		if(this.active) {
			this.fireEvent("update", this);
    }
		this.setActive(this.value.length > 0);
	},
	
	/**
	 * Returns true if the filter has enough configuration information to be activated.
	 * @return {Boolean}
	 */
	isActivatable: function() {
		return true;
	},
	
	/**
	 * Sets the status of the filter and fires that appropriate events.
	 * @param {Boolean} active        The new filter state.
	 * @param {Boolean} suppressEvent True to prevent events from being fired.
	 */
	setActive: function(active, suppressEvent) {
		if(this.active != active) {
			this.active = active;
			if(suppressEvent !== true) {
				this.fireEvent(active ? 'activate' : 'deactivate', this);
      }
		}
	},
	
	/**
	 * Get the value of the filter
	 * @return {Object} The 'serialized' form of this filter
	 */
	getValue: Ext.emptyFn,
	
	/**
	 * Set the value of the filter.
	 * @param {Object} data The value of the filter
	 */	
	setValue: Ext.emptyFn,
	
	/**
	 * Serialize the filter data for transmission to the server.
	 * @return {Object/Array} An object or collection of objects containing key value pairs representing
	 * 	the current configuration of the filter.
	 */
	serialize: Ext.emptyFn,
	
	/**
	 * Validates the provided Ext.data.Record against the filters configuration.
	 * @param {Ext.data.Record} record The record to validate
	 * @return {Boolean} True if the record is valid with in the bounds of the filter, false otherwise.
	 */
	 validateRecord: function(){return true;}
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.grid.filter.StringFilter = Ext.extend(Ext.grid.filter.Filter, {
	updateBuffer: 500,
	icon: '/img/small_icons/famfamfam/find.png',
	
	init: function() {
		var value = this.value = new Ext.menu.EditableItem({icon: this.icon});
		value.on('keyup', this.onKeyUp, this);
		this.menu.add(value);
		
		this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);
	},
	
	onKeyUp: function(event) {
		if(event.getKey() == event.ENTER){
			this.menu.hide(true);
			return;
		}
		this.updateTask.delay(this.updateBuffer);
	},
	
	isActivatable: function() {
		return this.value.getValue().length > 0;
	},
	
	fireUpdate: function() {		
		if(this.active) {
			this.fireEvent("update", this);
    }
		this.setActive(this.isActivatable());
	},
	
	setValue: function(value) {
		this.value.setValue(value);
		this.fireEvent("update", this);
	},
	
	getValue: function() {
		return this.value.getValue();
	},
	
	serialize: function() {
		var args = {type: 'string', value: this.getValue()};
		this.fireEvent('serialize', args, this);
		return args;
	},
	
	validateRecord: function(record) {
		var val = record.get(this.dataIndex);
		if(typeof val != "string") {
			return this.getValue().length == 0;
    }
		return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;
	}
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.grid.filter.DateFilter = Ext.extend(Ext.grid.filter.Filter, {
    /**
     * @cfg {Date} dateFormat
     * The date format applied to the menu's {@link Ext.menu.DateMenu}
     */
	dateFormat: 'm/d/Y',
    /**
     * @cfg {Object} pickerOpts
     * The config object that will be passed to the menu's {@link Ext.menu.DateMenu} during
     * initialization (sets minDate, maxDate and format to the same configs specified on the filter)
     */
	pickerOpts: {},
    /**
     * @cfg {String} beforeText
     * The text displayed for the "Before" menu item
     */
    beforeText: 'Before',
    /**
     * @cfg {String} afterText
     * The text displayed for the "After" menu item
     */
    afterText: 'After',
    /**
     * @cfg {String} onText
     * The text displayed for the "On" menu item
     */
    onText: 'On',
    /**
     * @cfg {Date} minDate
     * The minimum date allowed in the menu's {@link Ext.menu.DateMenu}
     */
    /**
     * @cfg {Date} maxDate
     * The maximum date allowed in the menu's {@link Ext.menu.DateMenu}
     */
	
	init: function() {
		var opts = Ext.apply(this.pickerOpts, {
			minDate: this.minDate, 
			maxDate: this.maxDate, 
			format:  this.dateFormat
		});
		var dates = this.dates = {
			'before': new Ext.menu.CheckItem({text: this.beforeText, menu: new Ext.menu.DateMenu(opts)}),
			'after':  new Ext.menu.CheckItem({text: this.afterText, menu: new Ext.menu.DateMenu(opts)}),
			'on':     new Ext.menu.CheckItem({text: this.onText, menu: new Ext.menu.DateMenu(opts)})
    };
				
		this.menu.add(dates.before, dates.after, "-", dates.on);
		
		for(var key in dates) {
			var date = dates[key];
			date.menu.on('select', this.onSelect.createDelegate(this, [date]), this);
  
      date.on('checkchange', function(){
        this.setActive(this.isActivatable());
			}, this);
		};
	},
  
	onSelect: function(date, menuItem, value, picker) {
    date.setChecked(true);
    var dates = this.dates;
    
    if(date == dates.on) {
      dates.before.setChecked(false, true);
      dates.after.setChecked(false, true);
    } else {
      dates.on.setChecked(false, true);
      
      if(date == dates.after && dates.before.menu.picker.value < value) {
        dates.before.setChecked(false, true);
      } else if (date == dates.before && dates.after.menu.picker.value > value) {
        dates.after.setChecked(false, true);
      }
    }
    
    this.fireEvent("update", this);
  },
  
	getFieldValue: function(field) {
		return this.dates[field].menu.picker.getValue();
	},
	
	getPicker: function(field) {
		return this.dates[field].menu.picker;
	},
	
	isActivatable: function() {
		return this.dates.on.checked || this.dates.after.checked || this.dates.before.checked;
	},
	
	setValue: function(value) {
		for(var key in this.dates) {
			if(value[key]) {
				this.dates[key].menu.picker.setValue(value[key]);
				this.dates[key].setChecked(true);
			} else {
				this.dates[key].setChecked(false);
			}
    }
	},
	
	getValue: function() {
		var result = {};
		for(var key in this.dates) {
			if(this.dates[key].checked) {
				result[key] = this.dates[key].menu.picker.getValue();
      }
    }	
		return result;
	},
	
	serialize: function() {
		var args = [];
		if(this.dates.before.checked) {
			args = [{type: 'date', comparison: 'lt', value: this.getFieldValue('before').format(this.dateFormat)}];
    }
		if(this.dates.after.checked) {
			args.push({type: 'date', comparison: 'gt', value: this.getFieldValue('after').format(this.dateFormat)});
    }
		if(this.dates.on.checked) {
			args = {type: 'date', comparison: 'eq', value: this.getFieldValue('on').format(this.dateFormat)};
    }

    this.fireEvent('serialize', args, this);
		return args;
	},
	
	validateRecord: function(record) {
		var val = record.get(this.dataIndex).clearTime(true).getTime();
		
		if(this.dates.on.checked && val != this.getFieldValue('on').clearTime(true).getTime()) {
			return false;
    }
		if(this.dates.before.checked && val >= this.getFieldValue('before').clearTime(true).getTime()) {
			return false;
    }
		if(this.dates.after.checked && val <= this.getFieldValue('after').clearTime(true).getTime()) {
			return false;
    }
		return true;
	}
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.grid.filter.ListFilter = Ext.extend(Ext.grid.filter.Filter, {
	labelField:  'text',
	loadingText: 'Loading...',
	loadOnShow:  true,
	value:       [],
	loaded:      false,
	phpMode:     false,
	
	init: function(){
		this.menu.add('<span class="loading-indicator">' + this.loadingText + '</span>');
		
		if(this.store && this.loadOnShow) {
		  this.menu.on('show', this.onMenuLoad, this);
		} else if(this.options) {
			var options = [];
			for(var i=0, len=this.options.length; i<len; i++) {
				var value = this.options[i];
				switch(Ext.type(value)) {
					case 'array':  
            options.push(value);
            break;
					case 'object':
            options.push([value.id, value[this.labelField]]);
            break;
					case 'string':
            options.push([value, value]);
            break;
				}
			}
			
			this.store = new Ext.data.Store({
				reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField])
			});
			this.options = options;
			this.menu.on('show', this.onMenuLoad, this);
		}
    
		this.store.on('load', this.onLoad, this);
		this.bindShowAdapter();
	},
	
	/**
	 * Lists will initially show a 'loading' item while the data is retrieved from the store. In some cases the
	 * loaded data will result in a list that goes off the screen to the right (as placement calculations were done
	 * with the loading item). This adaptor will allow show to be called with no arguments to show with the previous
	 * arguments and thusly recalculate the width and potentially hang the menu from the left.
	 * 
	 */
	bindShowAdapter: function() {
		var oShow = this.menu.show;
		var lastArgs = null;
		this.menu.show = function() {
			if(arguments.length == 0) {
				oShow.apply(this, lastArgs);
			} else {
				lastArgs = arguments;
				oShow.apply(this, arguments);
			}
		};
	},
	
	onMenuLoad: function() {
		if(!this.loaded) {
			if(this.options) {
				this.store.loadData(this.options);
      } else {
				this.store.load();
      }
		}
	},
	
	onLoad: function(store, records) {
		var visible = this.menu.isVisible();
		this.menu.hide(false);
		
		this.menu.removeAll();
		
		var gid = this.single ? Ext.id() : null;
		for(var i=0, len=records.length; i<len; i++) {
			var item = new Ext.menu.CheckItem({
				text: records[i].get(this.labelField), 
				group: gid, 
				checked: this.value.indexOf(records[i].id) > -1,
				hideOnClick: false
      });
			
			item.itemId = records[i].id;
			item.on('checkchange', this.checkChange, this);
						
			this.menu.add(item);
		}
		
		this.setActive(this.isActivatable());
		this.loaded = true;
		
		if(visible) {
			this.menu.show(); //Adaptor will re-invoke with previous arguments
    }
	},
	
	checkChange: function(item, checked) {
		var value = [];
		this.menu.items.each(function(item) {
			if(item.checked) {
				value.push(item.itemId);
      }
		},this);
		this.value = value;
		
		this.setActive(this.isActivatable());
		this.fireEvent("update", this);
	},
	
	isActivatable: function() {
		return this.value.length > 0;
	},
	
	setValue: function(value) {
		var value = this.value = [].concat(value);

		if(this.loaded) {
			this.menu.items.each(function(item) {
				item.setChecked(false, true);
				for(var i=0, len=value.length; i<len; i++) {
					if(item.itemId == value[i]) {
						item.setChecked(true, true);
          }
        }
			}, this);
    }
			
		this.fireEvent("update", this);
	},
	
	getValue: function() {
		return this.value;
	},
	
	serialize: function() {
    var args = {type: 'list', value: this.phpMode ? this.value.join(',') : this.value};
    this.fireEvent('serialize', args, this);
		return args;
	},
	
	validateRecord: function(record) {
		return this.getValue().indexOf(record.get(this.dataIndex)) > -1;
	}
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.grid.filter.NumericFilter = Ext.extend(Ext.grid.filter.Filter, {
	init: function() {
		this.menu = new Ext.menu.RangeMenu();
		
		this.menu.on("update", this.fireUpdate, this);
	},
	
	fireUpdate: function() {
		this.setActive(this.isActivatable());
		this.fireEvent("update", this);
	},
	
	isActivatable: function() {
		var value = this.menu.getValue();
		return value.eq !== undefined || value.gt !== undefined || value.lt !== undefined;
	},
	
	setValue: function(value) {
		this.menu.setValue(value);
	},
	
	getValue: function() {
		return this.menu.getValue();
	},
	
	serialize: function() {
		var args = [];
		var values = this.menu.getValue();
		for(var key in values) {
			args.push({type: 'numeric', comparison: key, value: values[key]});
    }
		this.fireEvent('serialize', args, this);
		return args;
	},
	
	validateRecord: function(record) {
		var val = record.get(this.dataIndex),
			values = this.menu.getValue();
			
		if(values.eq != undefined && val != values.eq) {
			return false;
    }
		if(values.lt != undefined && val >= values.lt) {
			return false;
    }
		if(values.gt != undefined && val <= values.gt) {
			return false;
    }
		return true;
	}
});/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.grid.filter.BooleanFilter = Ext.extend(Ext.grid.filter.Filter, {
    /**
     * @cfg {Boolean} defaultValue
     * The default value of this filter (defaults to false)
     */
    defaultValue: false,
    /**
     * @cfg {String} yesText
     * The text displayed for the "Yes" checkbox
     */
    yesText: 'Yes',
    /**
     * @cfg {String} noText
     * The text displayed for the "No" checkbox
     */
    noText: 'No',

	init: function(){
	    var gId = Ext.id();
			this.options = [
				new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}),
				new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})
	    ];
		
		this.menu.add(this.options[0], this.options[1]);
		
		for(var i=0; i<this.options.length; i++) {
			this.options[i].on('click', this.fireUpdate, this);
			this.options[i].on('checkchange', this.fireUpdate, this);
		}
	},
	
	isActivatable: function() {
		return true;
	},
	
	fireUpdate: function() {		
		this.fireEvent("update", this);			
		this.setActive(true);
	},
	
	setValue: function(value) {
		this.options[value ? 0 : 1].setChecked(true);
	},
	
	getValue: function() {
		return this.options[0].checked;
	},
	
	serialize: function() {
		var args = {type: 'boolean', value: this.getValue()};
		this.fireEvent('serialize', args, this);
		return args;
	},
	
	validateRecord: function(record) {
		return record.get(this.dataIndex) == this.getValue();
	}
}); 
// create namespace
Ext.namespace('pdnsgui');
 
// create application
pdnsgui.app = function() {
  // do NOT access DOM from here; elements don't exist yet
  
  // private variables
    
  var viewport;
  
  /* Multi Window */
Ext.ux.Window = function(cfg){

  if (!cfg) var cfg = {};
  
  var width = 300;
  
  if (cfg.items instanceof Array)
  {
    if (cfg.items[0].width) width = cfg.items[0].width;
  }
  else
  {
    if (cfg.items.width) width = cfg.items.width;
  }
  
  var defaultCfg = {
    layout:'fit',
    width: width + 10,
    resizable: false,
    plain: true,
    buttonAlign: 'center',
    listeners: {
      render: function(win){
        var map = new Ext.KeyMap(win.getEl(), {
            key: 13, // or Ext.EventObject.ENTER
            fn: function(key,e){
              
              var target = Ext.get(e.getTarget());
              
              if (target.dom.type == 'textarea')
              {
                return false;
              }
              
              if ('doSubmit' in win)
              {
                win.doSubmit();
              }
            },
            scope: win
        });
      },
      beforeShow: function() {
        if ((this.x == undefined) && (this.y == undefined))
        {
          
          this.y = 40;
          
          var vp_width = viewport.getSize()['width'];
          var win_width = this.getSize()['width'];
          
          this.x = (vp_width - win_width) / 2;
          
          var prev;
          this.manager.each(function(w) {
              if (w == this) {
                  if (prev) {
                    var o = 20;
                      var p = prev.getPosition();
                      this.x = p[0] + o;
                      this.y = p[1] + o;
                  }
                  return false;
              }
              if (w.isVisible()) prev = w;
          }, this);
        }
      }
    }
  };
  
  if (cfg.helpTopic)
  {
    defaultCfg.tools = [{
      id: 'help',
      handler: function(){
        getHelp(cfg.helpTopic);
      }
    }];
  }
  
  Ext.applyIf(cfg, defaultCfg);
  
  return new Ext.Window(cfg);
}

/**
 * Helper function which checks if window is already open
 */
function get_win_id(object_id)
{
  if (!object_id) object_id = '';
  else if (typeof object_id.id != 'undefined') object_id = object_id.id;
  
  var match = /function (.*)\(/i.exec(get_win_id.caller.toString());
  
  var win_id = match[1] + object_id;
  
  // check if not already open
  var win = Ext.WindowMgr.get(win_id);
  if (win)
  {
    Ext.fly(win.getEl()).frame("ff0000");
    win.toFront();
    return false;
  }
  
  return win_id;
}
  /* Records */
Ext.ux.RecordsGrid = function(cfg){

  if (!cfg) var cfg = {};
  
  if (cfg.template)
  {
    if (!cfg.records)
    {
      cfg.records = [
        { 
          name: '%DOMAIN%', 
          type: 'SOA',
          content: 'master.dns hostmaster.%DOMAIN% %SERIAL%',
          ttl: 3600        },{
          name: '%DOMAIN%', 
          type: 'NS',
          content: 'master.dns',
          ttl: 3600        },{
          name: '%DOMAIN%', 
          type: 'MX',
          content: 'mail.server',
          ttl: 3600,
          prio: 0
        }
      ];
    }
    
    var store = new Ext.data.JsonStore({
      fields : [ 'id','name','type','content','ttl','prio','needs_commit' ],
      root: 'records',
      data: cfg
    });
  }
  else
  {
    var store = new Ext.data.JsonStore({
      url: '/domain/listrecords',
      baseParams: { id: cfg.domain_id },
      fields : [ 'id','name','type','content','ttl','prio','needs_commit' ],
      root: 'Record',
      autoLoad: true
    });
  }
  
  var defaultCfg = {
    border: false,
    store: store,
    height: 260,
    loadMask: true,
    enableHdMenu: false,
    enableColumnMove: false,
    clicksToEdit: 1,
    columns: [
      {
        header: 'Name',
        dataIndex: 'name',
        editor: new Ext.form.TextField({
          allowBlank: false
        }),
        renderer: function(v, meta, r){
          if (r.data.needs_commit)
          {
            meta.attr = 'style="font-weight: bold;"';
          }
          
          return v;
        }
      },{
        header: 'Type',
        dataIndex: 'type',
        width: 50,
        fixed: true,
        editor: new Ext.ux.TypeCombo(),
        renderer: function(v, meta, r){
          if (r.data.needs_commit)
          {
            meta.attr = 'style="font-weight: bold;"';
          }
          
          return v;
        }
      },{
        header: 'Content',
        dataIndex: 'content',
        editor: new Ext.form.TextField({
          allowBlank: false
        }),
        renderer: function(v, meta, r){
          if (r.data.needs_commit)
          {
            meta.attr = 'style="font-weight: bold;"';
          }
          
          return v;
        }
      },{
        header: 'TTL',
        dataIndex: 'ttl',
        width: 50,
        fixed: true,
        editor: new Ext.form.TextField({
          allowBlank: false,
          maskRe: /^[0-9]$/
        }),
        renderer: function(v, meta, r){
          if (r.data.needs_commit)
          {
            meta.attr = 'style="font-weight: bold;"';
          }
          
          return v;
        }
      },{
        header: 'Prio',
        dataIndex: 'prio',
        width: 40,
        fixed: true,
        editor: new Ext.form.TextField({
          maskRe: /^[0-9]$/
        }),
        renderer: function(v, meta, r){
          if (r.data.needs_commit)
          {
            meta.attr = 'style="font-weight: bold;"';
          }
          
          return v;
        }
      },{
        id: 'delete',
        header: '',
        dataIndex: 'id',
        width: 24,
        fixed: true,
        renderer: function(v){
          return '<img src="/images/bin.gif" alt="Bin" />';
        }
      }
    ],
    listeners: {
      cellclick: function(grid, rowIndex, columnIndex, e){
        
        var columnId = grid.getColumnModel().getColumnId(columnIndex);
        
        /* Delete clicked */
        if (columnId == 'delete')
        {
                    var record = grid.getStore().getAt(rowIndex);
          grid.store.remove(record);
        }
      }
    },
    viewConfig:{
      forceFit: true,
      deferEmptyText: true,
      emptyText: 'No records to display'
    },
    bbar: [
      {
        xtype: 'button',
        text: 'Add record',
        iconCls: 'icon-add',
        handler: function(){
          grid.store.add(new grid.store.recordType({
            name: cfg.defaultName,
            type: 'A',
            ttl: 3600          }));
          
          grid.getView().focusRow(grid.store.getCount() - 1);
        }
      }
    ]
  };
  
  Ext.applyIf(cfg, defaultCfg);
  
  var grid = new Ext.grid.EditorGridPanel(cfg);
  
  return grid;
}
  /* Type Combo */
Ext.ux.TypeCombo = function(cfg){

  if (!cfg) var cfg = {};

  var defaultCfg = {
    store: RecordTypeStoreActive,
    displayField: 'id',
    valueField: 'id',
    width: 120,
    name: 'type',
    hiddenName: 'type',
    mode: 'local',
    triggerAction: 'all',
    forceSelection: true,
    editable: false,
    emptyText: 'Select...'
  };

  Ext.applyIf(cfg, defaultCfg);
  
  return new Ext.form.ComboBox(cfg);
}
  Ext.app.SearchField = Ext.extend(Ext.form.TwinTriggerField, {
    initComponent : function(){
        Ext.app.SearchField.superclass.initComponent.call(this);
        this.on('specialkey', function(f, e){
            if(e.getKey() == e.ENTER){
                this.onTrigger2Click();
            }
        }, this);
    },

    validationEvent:false,
    validateOnBlur:false,
    trigger1Class:'x-form-clear-trigger',
    trigger2Class:'x-form-search-trigger',
    hideTrigger1:true,
    width:180,
    pageSize: 20,
    hasSearch : false,
    paramName : 'search',

    onTrigger1Click : function(){
        if(this.hasSearch){
            this.el.dom.value = '';
            var o = {start: 0, limit: this.pageSize};
            this.store.baseParams = this.store.baseParams || {};
            this.store.baseParams[this.paramName] = '';
            this.store.reload({params:o});
            this.triggers[0].hide();
            this.hasSearch = false;
        }
    },

    onTrigger2Click : function(){
        var v = this.getRawValue();
        if(v.length < 1){
            this.onTrigger1Click();
            return;
        }
        var o = {start: 0, limit: this.pageSize};
        this.store.baseParams = this.store.baseParams || {};
        this.store.baseParams[this.paramName] = v;
        this.store.reload({params:o});
        this.hasSearch = true;
        this.triggers[0].show();
    }
});
  
  var DomainStore = new Ext.data.JsonStore({
  url: '/domain/list'
});
  var TemplateStore = new Ext.data.JsonStore({
  url: '/template/list'
});
  var RecordTypeStoreActive = new Ext.data.JsonStore({
  id: 'id',
  fields: [ 'id','state' ]
});

var RecordTypeStore = new Ext.data.JsonStore({
  url: '/template/recordtype',
  listeners: {
    load: function(store){
      
      RecordTypeStoreActive.removeAll();
      
      store.each(function(r){
        
        if (r.data.state == 1)
        {
          RecordTypeStoreActive.add(r);
        }
        
      });
    }
  }
});
  
  
function removeLoadingMask(){
  
  checkIE();
  
  checkFirebug();
  
  setTimeout(function(){
    
    Ext.get('loading').remove();
    Ext.get('loading-mask').fadeOut({remove:true});
    
  }, 100);

}

function loadStores(){
    
  var emergency = function(){
    
    removeLoadingMask();
    
    Ext.Msg.alert('Error', 'Application failed to initialized correctly. Please reload in a few moments, and if this message apears again, contact our support team.');
    
    Ext.Ajax.request({
      url: '/ext/error',
      params: { error: 'Stores failed to load in 7 seconds.' }
    });

  }
  
  var t = emergency.defer(7000);
  
  Ext.get('loading-msg').update('Loading Stores');
  
  var storesCount = 0;
  
  var stores = [
    'DomainStore',
    'TemplateStore',
    'RecordTypeStore'
  ];
  
  Ext.each(stores,function(store){
    eval(store + ".on('load',function(){ storesCount++;  Ext.get('loading-msg').update('Loading "+store+"'); if (storesCount == stores.length){ clearTimeout(t); removeLoadingMask(); } }," + store + ",{single: true});" + store + ".load();");
    
  });
  
};

function checkIE()
{
  if (Ext.isIE)
  {
    messages.add({
      html: 'Warning: Internet Explorer has known performance issues when used with PowerDNS GUI.  Mozilla Firefox or Apple Safari are recommended. <a href="#" class="hide">[ hide ]</a>',
      listeners: {
        render: function(panel){
          panel.body.on('click',function(e){
            var targetEl = Ext.get(e.getTarget());
            
            if (targetEl.hasClass('hide'))
            {
              messages.remove(panel);
              messages.doLayout();
            }
          });
        }
      }
    });
    
    messages.doLayout();
  }
}

function checkFirebug() {
  if(window.console && window.console.firebug)
  {
    messages.add({
      html: 'Warning: Firebug is known to cause performance issues with PowerDNS GUI.'
    });
    
    messages.doLayout();
  }
}
  
  var messages = new Ext.Panel({
  height: 55,
  border: false,
  defaults: { 
    border: false, 
    bodyStyle: 'background: red; color: white; font-weight: bold; padding: 3px;', 
    style: 'margin-bottom: 2px; margin-right: 5px;'
  },
  autoScroll: true
});

var toolsMenu = new Ext.menu.Menu({
  items: [
    {
      text: 'Search and replace',
      handler: function(){ ReplaceWindow() },
      iconCls: 'icon-find'
    },{
      text: 'History',
      handler: function(){ HistoryWindow() },
      iconCls: 'icon-book_open'
    },{
      text: 'Settings',
      handler: function(){ RecordTypeWindow() },
      iconCls: 'icon-cog'
    }
  ]
});

var NorthRegion = new Ext.Panel({
  region:'north',
  border: false,
  layout: 'hbox',
  defaults: { border: false },
  layoutConfig: {
    align: 'middle'
  },
  height: 60,
  items: [
    {
      style: 'margin-left: 5px;',
      html: '<img src="/images/logo.png" alt="Logo" />',
      width: 200,
      height: 60
    },{
      flex: 1,
      height: 60,
      bodyStyle: 'padding-top: 5px; padding-left: 15px;',
      items: messages
    },{
      xtype: 'button',
      text: 'Tools',
      menu: toolsMenu,
      width: 90,
      iconCls: 'icon-wrench',
      scale: 'medium',
      margins: '0 5 0 35'
    },{
      xtype: 'button',
      scale: 'medium',
      iconCls: 'icon-server_connect',
      text: 'Commit changes',
      width: 140,
      margins: '0 5 0 5',
      handler: function(){
        Ext.Ajax.request({
          url: '/domain/commit',
          success: function(action){
            
            var res = Ext.decode(action.responseText);
            
            if (res.success)
            {
              Ext.Msg.info('Info',res.info);
              
              DomainStore.reload();
            }
            else
            {
              Ext.Msg.showRequestErrors(res);
            }
          }
        });

      }
    }
  ]
});
  var SouthRegion = {
  region:'south',
  split: false,
  bodyStyle: 'text-align: center;',
  height: 30,
  border: false,
  items: [{ 
    border: false,
    html: '<span style="color: gray;">Copyright &copy; 2009 - </span> <a href="http://level7systems.co.uk" target="_blank" style="color: gray;">Level 7 Systems Ltd.</a>',
    style: 'margin-top: 5px;'
  }]
};
  var settingsMenu = new Ext.menu.Menu({
  items: [
    {
      text: 'Templates',
      handler: function(){ TemplateWindow() },
      iconCls: 'icon-brick'
    }
  ]
});

var West = new Ext.grid.GridPanel({
  title: 'Domains',
  iconCls: 'icon-world',
  store: DomainStore,
  hideHeaders: true,
  disableSelection: true,
  columns: [
    {
      id: 'domain',
      dataIndex: 'name',
      width: 180,
      renderer: function(v, meta, r){
        if (r.data.needs_commit)
        {
          meta.attr = 'style="font-weight: bold;"';
        }
        
        return v;
      }
    }
  ],
  viewConfig:{
    forceFit: true,
    scrollOffset: 1,
    emptyText: 'No domains to display.'
  },
  listeners: {
    cellclick: function(grid, rowIndex, columnIndex, e){
      
      var columnId = grid.getColumnModel().getColumnId(columnIndex);
      
      if (columnId == 'domain')
      {
        var record = grid.getStore().getAt(rowIndex).data;
        
        DomainWindow(record);
      }
    }
  },
  tbar: [
    {
      text: 'Settings',
      iconCls: 'icon-cog',
      menu: settingsMenu
    },{
      xtype: 'tbfill'
    },{
      text: 'Add domain',
      iconCls: 'icon-add',
      handler: function(){
        AddDomainWindow();
      }
    }
  ]
});
  var Start = new Ext.Panel({
  bodyStyle: 'padding: 20px;',
  border: false,
  html: 'Click on the domain name in the left panel to start...<div style="font-size: 14px; margin-top: 60px;">This is a demo of PowerDNS GUI. Not a real DNS service</div><div style="margin-top: 20px;">Project home at <a href="http://code.google.com/p/pdns-gui/" target="_blank">http://code.google.com/p/pdns-gui/</a></div>'
});


var Tabs = new Ext.TabPanel({
  deferredRender: false,
  enableTabScroll:true,
  layoutOnTabChange: true,
  defaults: { autoScroll:true, hideMode: 'offsets' },
  plain: true
});
  
  function AddDomainWindow()
{
  var win_id = get_win_id();
  if (!win_id) return;
  
  var templatesCombo = new Ext.form.ComboBox({
    store: TemplateStore,
    fieldLabel: 'Template',
    width: 170,
    displayField: 'name',
    valueField: 'id',
    name: 'template_id',
    hiddenName: 'template_id',
    mode: 'local',
    triggerAction: 'all',
    forceSelection: true,
    editable: false,
    emptyText: 'Please select...'
  });
  
  if (TemplateStore.getCount() > 0)
  {
    var template = TemplateStore.getAt(0);
    
    templatesCombo.setValue(template.data.id);
  }
  
  var form = new Ext.form.FormPanel({
    bodyStyle: 'padding: 10px;',
    border: false,
    height: 80,
    labelWidth: 60,
    url: '/domain/add',
    defaults: { allowBlank: false },
    items: [
      {
        xtype: 'textfield',
        fieldLabel: 'Name',
        width: 170,
        name: 'name'
      },
        templatesCombo
    ]
  });
  
  var win = new Ext.ux.Window({
    id: win_id,
    title: 'Add domain',
    width: 300,
    doSubmit: function(){
      form.form.submit({
        success: function(form,action){
          win.close();
          
          DomainStore.load();
        }
      });
    },
    items: form,
    buttons: [
      {
        text: 'Submit',
        handler: function() { win.doSubmit() }
      },{
        text: 'Close',
        handler: function() { win.close() }
      }
    ]
  });
  
  win.show();
}
  function DomainWindow(domain)
{
  var win_id = get_win_id(domain);
  if (!win_id) return;
  
  var form = new Ext.form.FormPanel({
    height: 260,
    border: false,
    url: '/domain/edit',
    defaults: { allowBlank: false },
    items: [
      {
        xtype: 'hidden',
        name: 'id',
        value: domain.id
      },{
        layout: 'fit',
        border: false,
        items: new Ext.ux.RecordsGrid({
          defaultName: domain.name,
          border: false,
          domain_id: domain.id
        })
      }
    ]
  });
  
  var win = new Ext.ux.Window({
    id: win_id,
    title: domain.name + ' ('+domain.type+')',
    width: 450,
    resizable: true,
    items: form,
    doSubmit: function(){
      // remove all hidden fields
      Ext.each(form.find('xtype','hidden'),function(hidden){
        if (hidden.name != 'id')
        {
          form.remove(hidden);
        }
      });
      
      form.doLayout();
      
      var grid = form.items.items[form.items.items.length-1].items.items[0];
      
      var i = 0;
      grid.store.each(function(r){
        
        form.add({
          xtype: 'hidden',
          name: 'record['+i+'][id]',
          value: r.data.id
        });
        
        form.add({
          xtype: 'hidden',
          name: 'record['+i+'][name]',
          value: r.data.name
        });
        
        form.add({
          xtype: 'hidden',
          name: 'record['+i+'][type]',
          value: r.data.type
        });
        
        form.add({
          xtype: 'hidden',
          name: 'record['+i+'][content]',
          value: r.data.content
        });
        
        form.add({
          xtype: 'hidden',
          name: 'record['+i+'][ttl]',
          value: r.data.ttl
        });
        
        form.add({
          xtype: 'hidden',
          name: 'record['+i+'][prio]',
          value: r.data.prio
        });
        
        i++;
      });
      
      form.doLayout();
      
      form.form.submit({
        success: function(form, action){
          
          win.close();
          
          DomainStore.load();
        },
        failure: function(form, action){
          
          Ext.Msg.alert('Error',action.result.errors.record);
        }
      });
      
    },
    buttons: [
      {
        text: 'Save',
        handler: function() { win.doSubmit() }
      },{
        text: 'Close',
        handler: function() { win.close() }
      }
    ],
    tools: [{
      id: 'gear',
      handler: function(e,el){
        var menu = new Ext.menu.Menu({
          items: [
            {
              text: 'Delete',
              iconCls: 'icon-bin',
              handler: function(){
                
                // Show a dialog using config options:
                Ext.Msg.show({
                  title:'Confirm',
                  closable: false,
                  msg: 'Are you sure you want to delete this domain.',
                  buttons: Ext.Msg.YESNO,
                  icon: Ext.MessageBox.QUESTION,
                  fn: function(button){
                                        if (button == 'yes')
                    {
                      Ext.Ajax.request({
                        url: '/domain/delete',
                        success: function(r){
                          var response = Ext.decode(r.responseText);
                          
                          if (response.success)
                          {
                            win.close()
                            DomainStore.reload();
                          }
                          else
                          {
                            Ext.Msg.alert('Error',"Operation failed");
                          }
                        },
                        failure: function(){
                          Ext.Msg.alert('Error','Request failed.');
                        },
                        params: { id: domain.id }
                      });
                    }
                  }
                });
                
              }
            }
          ]
        });
        
        menu.showAt(e.xy);
      }
    }]
  });
  
  win.show();
  
  win.addListener('resize',function(win,width,height){
    form.items.items[1].items.items[0].setHeight(height-72);
  });
  
  
}
  function TemplateWindow()
{
  var win_id = get_win_id();
  if (!win_id) return;
  
  var Tabs = new Ext.TabPanel({
    activeTab: 0,
    border: false,
    enableTabScroll: true,
    plain: true,
    height: 358
  });
  
  if (TemplateStore.getCount() == 0)
  {
    Tabs.add(emptyTemplate('Default'));
  }
  else
  {
    TemplateStore.each(function(r){
      Tabs.add(existingTemplate(r.data));
    });
  }
  
  Tabs.doLayout();
  
  var win = new Ext.ux.Window({
    id: win_id,
    title: 'Templates',
    width: 450,
    items: Tabs,
    doSubmit: function(){
      
      var form_count = 0;
      
      var errors = '';
      
      Ext.each(Tabs.items.items,function(tab)
      {
        
        Ext.each(tab.items.items[0], function(form)
        {
          // remove all hidden fields
          Ext.each(form.find('xtype','hidden'),function(hidden){
            if (hidden.name != 'id')
            {
              form.remove(hidden);
            }
          });
          
          form.doLayout();
          
          var grid = tab.items.items[1].items.items[0];
          
          var i = 0;
          grid.store.each(function(r){
            
            form.add({
              xtype: 'hidden',
              name: 'record['+i+'][id]',
              value: r.data.id
            });
            
            form.add({
              xtype: 'hidden',
              name: 'record['+i+'][name]',
              value: r.data.name
            });
            
            form.add({
              xtype: 'hidden',
              name: 'record['+i+'][type]',
              value: r.data.type
            });
            
            form.add({
              xtype: 'hidden',
              name: 'record['+i+'][content]',
              value: r.data.content
            });
            
            form.add({
              xtype: 'hidden',
              name: 'record['+i+'][ttl]',
              value: r.data.ttl
            });
            
            form.add({
              xtype: 'hidden',
              name: 'record['+i+'][prio]',
              value: r.data.prio
            });
            
            i++;
          });
          
          form.doLayout();
          
          form.form.submit({
            success: function(form, action){
              
              form_count++;
              
              if (form_count == Tabs.items.items.length)
              {
                win.close();
                
                Ext.Msg.alert('Info',action.result.info);
                
                TemplateStore.load();
              }
            },
            failure: function(form, action){
              
              form_count++;
              
              if (form.url.match(/edit$/))
              {
                var template_name = form.items.items[1].getValue();
              }
              else
              {
                var template_name = form.items.items[0].getValue();
              }
              
              errors+= 'Template: <b>' + template_name + '</b><br/>';
              errors+= action.result.errors.record + '<br/>';
              
              if (form_count == Tabs.items.items.length)
              {
                Ext.Msg.alert('Error',errors);
              }
            }
          });
        });
      
      });
      
    },
    buttons: [
      {
        text: 'Add template',
        iconCls: 'icon-add',
        style: 'margin-right: 150px;',
        handler: function() {
          var grid = emptyTemplate('Default ' + Tabs.items.length);
          Tabs.add(grid);
          Tabs.activate(grid);
        }
      },{
        text: 'Save',
        handler: function() { win.doSubmit() }
      },{
        text: 'Close',
        handler: function() { win.close() }
      }
    ]
  });
  
  win.show();
}

function emptyTemplate(name)
{ 
  var form = new Ext.form.FormPanel({
    url: '/template/add',
    border: false,
    labelWidth: 60,
    bodyStyle: 'padding: 10px;',
    items: [
      {
        xtype: 'textfield',
        fieldLabel: 'Name',
        name: 'name',
        allowBlank: false,
        value: name
      },{
        xtype: 'combo',
        store: [["NATIVE","Native"],["MASTER","Master"],["SLAVE","Slave"]],
        fieldLabel: 'Type',
        displayField: 'field2',
        valueField: 'field1',
        width: 120,
        name: 'type',
        hiddenName: 'type',
        mode: 'local',
        triggerAction: 'all',
        forceSelection: true,
        editable: false,
        allowBlank: false,
        value: 'MASTER'
      }
    ]
  });
  
  var template = new Ext.Panel({
    title: name,
    border: false,
    items: [
      form,
      {
        layout: 'fit',
        border: false,
        items: new Ext.ux.RecordsGrid({
          defaultName: '%DOMAIN%',
          template: true
        })
      }
    ]
  });
  
  return template;
}


function existingTemplate(template)
{ 
  var form = new Ext.form.FormPanel({
    url: '/template/edit',
    border: false,
    labelWidth: 60,
    bodyStyle: 'padding: 10px;',
    items: [
      {
        xtype: 'hidden',
        name: 'id',
        value: template.id
      },{
        xtype: 'textfield',
        fieldLabel: 'Name',
        name: 'name',
        allowBlank: false,
        value: template.name
      },{
        xtype: 'combo',
        store: [["NATIVE","Native"],["MASTER","Master"],["SLAVE","Slave"]],
        fieldLabel: 'Type',
        displayField: 'field2',
        valueField: 'field1',
        width: 120,
        name: 'type',
        hiddenName: 'type',
        mode: 'local',
        triggerAction: 'all',
        forceSelection: true,
        editable: false,
        allowBlank: false,
        value: template.type
      }
    ]
  });
  
  var template = new Ext.Panel({
    title: template.name,
    border: false,
    items: [
      form,
      {
        layout: 'fit',
        border: false,
        items: new Ext.ux.RecordsGrid({
          records: template.records, 
          defaultName: '%DOMAIN%', 
          border: false,
          template: true
        })
      }
    ]
  });
  
  return template;
}

  function ReplaceWindow()
{
  var win_id = get_win_id();
  if (!win_id) return;
  
  var form = new Ext.form.FormPanel({
    url: '/domain/replace',
    height: 210,
    labelWidth: 60,
    border: false,
    bodyStyle: 'padding: 10px;',
    items: [
      {
        xtype: 'fieldset',
        title: 'Search for...',
        defaults: { allowBlank: false },
        items: [
          new Ext.ux.TypeCombo({
            name: 'search_type',
            fieldLabel: 'Type',
            hiddenName: 'search_type'
          }),
          {
            xtype: 'textfield',
            fieldLabel: 'Content',
            name: 'search_content'
          }
        ]
      },{
        xtype: 'fieldset',
        title: 'Replace with...',
        defaults: { allowBlank: false },
        items: [
          new Ext.ux.TypeCombo({
            name: 'replace_type',
            fieldLabel: 'Type',
            hiddenName: 'replace_type'
          }),
          {
            xtype: 'textfield',
            fieldLabel: 'Content',
            name: 'replace_content'
          }
        ]
      }
    ]
  });
  
  var win = new Ext.ux.Window({
    id: win_id,
    title: 'Search and replace',
    width: 300,
    doSubmit: function(){
      form.form.submit({
        success: function(form,action){
          win.close();
          
          Ext.Msg.minWidth = 360;
          
          Ext.Msg.alert('Info',action.result.info);
          
          DomainStore.load();
        }
      });
    },
    items: form,
    buttons: [
      {
        text: 'Submit',
        handler: function() { win.doSubmit() }
      },{
        text: 'Close',
        handler: function() { win.close() }
      }
    ]
  });
  
  win.show();
}
  function HistoryWindow()
{
  var win_id = get_win_id();
  if (!win_id) return;
  
  var auditStore = new Ext.data.JsonStore({
    url: '/ext/audit',
    autoLoad: true,
    baseParams: { start: 0, limit: 20 }
  });
  
  var grid = new Ext.grid.GridPanel({
		height: 532,
		border: false,
    loadMask: true,
		store: auditStore,
		columns: [
			{
				header: 'Timestamp',
				dataIndex: 'created_at',
        menuDisabled: true,
				width: 110,
				fixed: true
			},{
				header: '',
				dataIndex: 'type',
				width: 30,
				fixed:true,
        renderer: function(v){
          switch (v){
            case 'ADD':
              return '<div style="width: 16px; height: 16px;" class="icon-add" ext:qtip="Added" />'
              break;
            case 'UPDATE':
              return '<div style="width: 16px; height: 16px;" class="icon-cog" ext:qtip="Updated" />'
              break;
            case 'DELETE':
              return '<div style="width: 16px; height: 16px;" class="icon-bin" ext:qtip="Deleted" />'
              break;
            default:
              return '?';
          }
        }
			},{
				header: 'Name',
        menuDisabled: true,
        width: 120,
        fixed: true,
				dataIndex: 'changes',
				renderer: function(v){
          return v.Name;
				}
			},{
				header: 'Type',
        menuDisabled: true,
        width: 50,
        fixed: true,
				dataIndex: 'changes',
				renderer: function(v){
          return v.Type;
				}
			},{
				header: 'Content',
        menuDisabled: true,
				dataIndex: 'changes',
				renderer: function(v){
          return v.Content;
				}
			},{
				header: 'TTL',
        width: 40,
        fixed: true,
        menuDisabled: true,
				dataIndex: 'changes',
				renderer: function(v){
          return v.Ttl;
				}
			},{
				header: 'Prio',
        width: 30,
        fixed: true,
        menuDisabled: true,
				dataIndex: 'changes',
				renderer: function(v){
          return v.Prio;
				}
			}
		],
    viewConfig: {
      forceFit: true,
      scrollOffset: 1,
      emptyText: 'No items to display.'
		},
    bbar: new Ext.PagingToolbar({
      store: auditStore,
      displayInfo: true,
      displayMsg: 'Displaying items {0} - {1} of {2}',
      emptyMsg: 'No items to display.'
    })
  });
	
  var win = new Ext.ux.Window({
    id: win_id,
    title: 'History',
    width: 650,
    items: grid,
    resizable: false,
    buttons: [
      new Ext.app.SearchField({
        store: auditStore,
        width: 140
      }),{
        style: 'margin-left: 140px;',
        text: 'Close',
        handler: function() { win.close() }
      }
    ]
  });
  
  win.show();
  
}
  function RecordTypeWindow()
{
  var win_id = get_win_id();
  if (!win_id) return;
  
  var form = new Ext.form.FormPanel({
    bodyStyle: 'padding: 10px;',
    autoScroll: true,
    border: false,
    height: 400,
    labelWidth: 60,
    url: '/template/recordtype',
    items: [
              {
          xtype: 'xcheckbox',
          fieldLabel: 'A',
          boxLabel: "The A record contains an IP address. It is stored as a decimal dotted quad string, for example: '213.244.168.210'.",
          name: 'record_type[A]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'AAAA',
          boxLabel: "The AAAA record contains an IPv6 address. An example: '3ffe:8114:2000:bf0::1'.",
          name: 'record_type[AAAA]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'AFSDB',
          boxLabel: "Specialised record type for the 'Andrew Filesystem'. Stored as: '#subtype hostname', where subtype is a number. ",
          name: 'record_type[AFSDB]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'CERT',
          boxLabel: "Specialised record type for storing certificates, defined in RFC 2538.",
          name: 'record_type[CERT]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'CNAME',
          boxLabel: "The CNAME record specifies the canonical name of a record. It is stored plainly. Like all other records, it is not terminated by a dot. A sample might be 'webserver-01.yourcompany.com'.",
          name: 'record_type[CNAME]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'DNSKEY',
          boxLabel: "The DNSKEY DNSSEC record type is fully supported, as described in RFC 3757. Note that while PowerDNS can store, retrieve and serve DNSSEC records, no further DNSSEC processing is performed.",
          name: 'record_type[DNSKEY]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'DS',
          boxLabel: "The DS DNSSEC record type is fully supported, as described in RFC 3757. Note that while PowerDNS can store, retrieve and serve DNSSEC records, no further DNSSEC processing is performed.",
          name: 'record_type[DS]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'HINFO',
          boxLabel: "Hardware Info record, used to specify CPU and operating system. Stored with a single space separating these two, example: 'i386 Linux'.",
          name: 'record_type[HINFO]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'KEY',
          boxLabel: "The KEY record is fully supported. For its syntax, see RFC 2535.",
          name: 'record_type[KEY]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'LOC',
          boxLabel: "The LOC record is fully supported. For its syntax, see RFC 1876. A sample content would be: '51 56 0.123 N 5 54 0.000 E 4.00m 1.00m 10000.00m 10.00m'",
          name: 'record_type[LOC]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'MX',
          boxLabel: "The MX record specifies a mail exchanger host for a domain. Each mail exchanger also has a priority or preference. This should be specified in the separate field dedicated for that purpose, often called 'prio'.",
          name: 'record_type[MX]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'NAPTR',
          boxLabel: "Naming Authority Pointer, RFC 2915.",
          name: 'record_type[NAPTR]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'NS',
          boxLabel: "Nameserver record. Specifies nameservers for a domain.",
          name: 'record_type[NS]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'NSEC',
          boxLabel: "The NSEC DNSSEC record type is fully supported, as described in RFC 3757.",
          name: 'record_type[NSEC]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'PTR',
          boxLabel: "Reverse pointer, used to specify the host name belonging to an IP or IPv6 address.",
          name: 'record_type[PTR]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'RP',
          boxLabel: "Responsible Person record, as described in RFC 1183. Stored with a single space between the mailbox name and the more-information pointer.",
          name: 'record_type[RP]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'RRSIG',
          boxLabel: "The RRSIG DNSSEC record type is fully supported, as described in RFC 3757.",
          name: 'record_type[RRSIG]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'SOA',
          boxLabel: "The Start of Authority record is one of the most complex available. It specifies a lot about a domain: the name of the master nameserver ('the primary'), the hostmaster and a set of numbers indicating how the data in this domain expires and how often it needs to be checked. Further more, it contains a serial number which should rise on each change of the domain.",
          name: 'record_type[SOA]',
          height: 80        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'SPF',
          boxLabel: "SPF records can be used to store Sender Permitted From details.",
          name: 'record_type[SPF]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'SSHFP',
          boxLabel: "The SSHFP record type, used for storing Secure Shell (SSH) fingerprints, is fully supported.",
          name: 'record_type[SSHFP]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'SRV',
          boxLabel: "SRV records can be used to encode the location and port of services on a domain name. When encoding, the priority field is used to encode the priority.",
          name: 'record_type[SRV]',
          height: 40        },
              {
          xtype: 'xcheckbox',
          fieldLabel: 'TXT',
          boxLabel: "The TXT field can be used to attach textual data to a domain. Text is stored plainly.",
          name: 'record_type[TXT]',
          height: 40        },
          ]
  });
  
  Ext.each(form.find('xtype','xcheckbox'),function(field){
    
    var r = RecordTypeStore.getById(field.fieldLabel);

    field.checked = r.data.state;
  });
  
  var win = new Ext.ux.Window({
    id: win_id,
    title: 'Record types',
    width: 700,
    doSubmit: function(){
      form.form.submit({
        success: function(form,action){
          win.close();
          
          RecordTypeStore.load();
        }
      });
    },
    items: form,
    buttons: [
      {
        text: 'Update',
        handler: function() { win.doSubmit() }
      },{
        text: 'Close',
        handler: function() { win.close() }
      }
    ]
  });
  
  win.show();
}

  // private functions

  // public space
  return {
    // public properties, e.g. strings to translate
    
    // public methods
    init: function() {
      
            
      Ext.QuickTips.init();
      
      

/* Viewport */
viewport = new Ext.Viewport({
  layout: 'border',
  style:  'background: #FFFFFF;',
  items:[
    NorthRegion,
    {
      region:   'west',
      layout:   'fit',
      defaults: { border: true },
      margins:  '0 5 0 5',
      width:    200,
      border:   false,
      items: West
    },{
      region: 'center',
      border: false,
      margins: "0 5 0 0",
      bodyStyle: 'padding: 20px;',
      items: Start
    },
    SouthRegion
  ],
  listeners: {
    render: function(){
      loadStores();
    }
  }
});
      
    } // end of init
  };
}(); // end of app

Ext.onReady(pdnsgui.app.init, pdnsgui.app);

