//Ext.Ajax.timeout = 120000; // 2 minute timeout (default is 30 seconds)
Ext.Ajax.timeout = 90000; // 1.5 minute timeout (default is 30 seconds)

/*
 * Copyright (c) 2010 Nick Galbreath
 * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/* base64 encode/decode compatible with window.btoa/atob
 *
 * window.atob/btoa is a Firefox extension to convert binary data (the "b")
 * to base64 (ascii, the "a").
 *
 * It is also found in Safari and Chrome.  It is not available in IE.
 *
 * if (!window.btoa) window.btoa = base64.encode
 * if (!window.atob) window.atob = base64.decode
 *
 * The original spec's for atob/btoa are a bit lacking
 * https://developer.mozilla.org/en/DOM/window.atob
 * https://developer.mozilla.org/en/DOM/window.btoa
 *
 * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
 * If any character is not [0,255], then an DOMException(5) is thrown.
 *
 * window.atob and base64.decode take a base64-encoded string
 * If the input length is not a multiple of 4, or contains invalid characters
 *   then an DOMException(5) is thrown.
 */
var base64 = {};
base64.PADCHAR = '=';
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

base64.makeDOMException = function() {
    // sadly in FF,Safari,Chrome you can't make a DOMException
    var e, tmp;

    try {
        return new DOMException(DOMException.INVALID_CHARACTER_ERR);
    } catch (tmp) {
        // not available, just passback a duck-typed equiv
        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
        var ex = new Error("DOM Exception 5");

        // ex.number and ex.description is IE-specific.
        ex.code = ex.number = 5;
        ex.name = ex.description = "INVALID_CHARACTER_ERR";

        // Safari/Chrome output format
        ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
        return ex;
    }
}

base64.getbyte64 = function(s,i) {
    // This is oddly fast, except on Chrome/V8.
    //  Minimal or no improvement in performance by using a
    //   object with properties mapping chars to value (eg. 'A': 0)
    var idx = base64.ALPHA.indexOf(s.charAt(i));
    if (idx === -1) {
        throw base64.makeDOMException();
    }
    return idx;
}

base64.decode = function(s) {
    // convert to string
    s = '' + s;
    var getbyte64 = base64.getbyte64;
    var pads, i, b10;
    var imax = s.length
    if (imax === 0) {
        return s;
    }

    if (imax % 4 !== 0) {
        throw base64.makeDOMException();
    }

    pads = 0
    if (s.charAt(imax - 1) === base64.PADCHAR) {
        pads = 1;
        if (s.charAt(imax - 2) === base64.PADCHAR) {
            pads = 2;
        }
        // either way, we want to ignore this last block
        imax -= 4;
    }

    var x = [];
    for (i = 0; i < imax; i += 4) {
        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
    }

    switch (pads) {
    case 1:
        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
        break;
    case 2:
        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
        x.push(String.fromCharCode(b10 >> 16));
        break;
    }
    return x.join('');
}

base64.getbyte = function(s,i) {
    var x = s.charCodeAt(i);
    if (x > 255) {
        throw base64.makeDOMException();
    }
    return x;
}

base64.encode = function(s) {
    if (arguments.length !== 1) {
        throw new SyntaxError("Not enough arguments");
    }
    var padchar = base64.PADCHAR;
    var alpha   = base64.ALPHA;
    var getbyte = base64.getbyte;

    var i, b10;
    var x = [];

    // convert to string
    s = '' + s;

    var imax = s.length - s.length % 3;

    if (s.length === 0) {
        return s;
    }
    for (i = 0; i < imax; i += 3) {
        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
        x.push(alpha.charAt(b10 >> 18));
        x.push(alpha.charAt((b10 >> 12) & 0x3F));
        x.push(alpha.charAt((b10 >> 6) & 0x3f));
        x.push(alpha.charAt(b10 & 0x3f));
    }
    switch (s.length - imax) {
    case 1:
        b10 = getbyte(s,i) << 16;
        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
               padchar + padchar);
        break;
    case 2:
        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
        break;
    }
    return x.join('');
}


/*!
 * Ext JS Library 3.1.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.grid');

/**
 * @class Ext.ux.grid.RowExpanderEX
 * @extends Ext.util.Observable
 * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
 * a second row body which expands/contracts.  The expand/contract behavior is configurable to react
 * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
 *
 * @ptype rowexpander
 */
Ext.ux.grid.RowExpanderEX = Ext.extend(Ext.util.Observable, {
    /**
     * @cfg {Boolean} expandOnEnter
     * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
     * key is pressed (defaults to <tt>true</tt>).
     */
    expandOnEnter : true,
    /**
     * @cfg {Boolean} expandOnDblClick
     * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
     * (defaults to <tt>true</tt>).
     */
    expandOnDblClick : true,

    header : '',
    width : 20,
    sortable : false,
    fixed : true,
    menuDisabled : true,
    dataIndex : '',
    id : 'expander',
    lazyRender : true,
    enableCaching : true,

    constructor: function(config){
        Ext.apply(this, config);

        this.addEvents({
            /**
             * @event beforeexpand
             * Fires before the row expands. Have the listener return false to prevent the row from expanding.
             * @param {Object} this RowExpander object.
             * @param {Object} Ext.data.Record Record for the selected row.
             * @param {Object} body body element for the secondary row.
             * @param {Number} rowIndex The current row index.
             */
            beforeexpand: true,
            /**
             * @event expand
             * Fires after the row expands.
             * @param {Object} this RowExpander object.
             * @param {Object} Ext.data.Record Record for the selected row.
             * @param {Object} body body element for the secondary row.
             * @param {Number} rowIndex The current row index.
             */
            expand: true,
            /**
             * @event beforecollapse
             * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.
             * @param {Object} this RowExpander object.
             * @param {Object} Ext.data.Record Record for the selected row.
             * @param {Object} body body element for the secondary row.
             * @param {Number} rowIndex The current row index.
             */
            beforecollapse: true,
            /**
             * @event collapse
             * Fires after the row collapses.
             * @param {Object} this RowExpander object.
             * @param {Object} Ext.data.Record Record for the selected row.
             * @param {Object} body body element for the secondary row.
             * @param {Number} rowIndex The current row index.
             */
            collapse: true
        });

        Ext.ux.grid.RowExpanderEX.superclass.constructor.call(this);

        if(this.tpl){
            if(typeof this.tpl == 'string'){
                this.tpl = new Ext.Template(this.tpl);
            }
            this.tpl.compile();
        }

        this.state = {};
        this.bodyContent = {};
    },

    getRowClass : function(record, rowIndex, p, ds){
        p.cols = p.cols-1;
        var content = this.bodyContent[record.id];
        if(!content && !this.lazyRender){
            content = this.getBodyContent(record, rowIndex);
        }
        if(content){
            p.body = content;
        }
        // "RowExpanderEX" code:
        var baseclass = this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
        if (this.getRowClass_eval) {
			return baseclass + ' ' + eval(this.getRowClass_eval);
        }
        return baseclass;
        // --------------------
    },

    init : function(grid){
        this.grid = grid;

        var view = grid.getView();
        view.getRowClass = this.getRowClass.createDelegate(this);

        view.enableRowBody = true;


        grid.on('render', this.onRender, this);
        grid.on('destroy', this.onDestroy, this);
    },

    // @private
    onRender: function() {
        var grid = this.grid;
        var mainBody = grid.getView().mainBody;
        mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});
        if (this.expandOnEnter) {
            this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {
                'enter' : this.onEnter,
                scope: this
            });
        }
        if (this.expandOnDblClick) {
            grid.on('rowdblclick', this.onRowDblClick, this);
        }
    },
    
    // @private    
    onDestroy: function() {
        if(this.keyNav){
            this.keyNav.disable();
            delete this.keyNav;
        }
        /*
         * A majority of the time, the plugin will be destroyed along with the grid,
         * which means the mainBody won't be available. On the off chance that the plugin
         * isn't destroyed with the grid, take care of removing the listener.
         */
        var mainBody = this.grid.getView().mainBody;
        if(mainBody){
            mainBody.un('mousedown', this.onMouseDown, this);
        }
    },
    // @private
    onRowDblClick: function(grid, rowIdx, e) {
        this.toggleRow(rowIdx);
    },

    onEnter: function(e) {
        var g = this.grid;
        var sm = g.getSelectionModel();
        var sels = sm.getSelections();
        for (var i = 0, len = sels.length; i < len; i++) {
            var rowIdx = g.getStore().indexOf(sels[i]);
            this.toggleRow(rowIdx);
        }
    },

    getBodyContent : function(record, index){
        if(!this.enableCaching){
            return this.tpl.apply(record.data);
        }
        var content = this.bodyContent[record.id];
        if(!content){
            content = this.tpl.apply(record.data);
            this.bodyContent[record.id] = content;
        }
        return content;
    },

    onMouseDown : function(e, t){
        e.stopEvent();
        var row = e.getTarget('.x-grid3-row');
        this.toggleRow(row);
    },

    renderer : function(v, p, record){
        p.cellAttr = 'rowspan="2"';
        return '<div class="x-grid3-row-expander">&#160;</div>';
    },

    beforeExpand : function(record, body, rowIndex){
        if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){
            if(this.tpl && this.lazyRender){
                body.innerHTML = this.getBodyContent(record, rowIndex);
            }
            return true;
        }else{
            return false;
        }
    },

    toggleRow : function(row){
        if(typeof row == 'number'){
            row = this.grid.view.getRow(row);
        }
        this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
    },

    expandRow : function(row){
        if(typeof row == 'number'){
            row = this.grid.view.getRow(row);
        }
        var record = this.grid.store.getAt(row.rowIndex);
        var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
        if(this.beforeExpand(record, body, row.rowIndex)){
            this.state[record.id] = true;
            Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
            this.fireEvent('expand', this, record, body, row.rowIndex);
        }
    },

    collapseRow : function(row){
        if(typeof row == 'number'){
            row = this.grid.view.getRow(row);
        }
        var record = this.grid.store.getAt(row.rowIndex);
        var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
        if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){
            this.state[record.id] = false;
            Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
            this.fireEvent('collapse', this, record, body, row.rowIndex);
        }
    }
});

Ext.preg('rowexpander', Ext.ux.grid.RowExpanderEX);

//backwards compat
Ext.grid.RowExpanderEX = Ext.ux.grid.RowExpanderEX;
// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
 * @class Ext.ux.grid.RowActions
 * @extends Ext.util.Observable
 *
 * RowActions plugin for Ext grid. Contains renderer for icons and fires events when an icon is clicked.
 * CSS rules from Ext.ux.RowActions.css are mandatory
 *
 * Important general information: Actions are identified by iconCls. Wherever an <i>action</i>
 * is referenced (event argument, callback argument), the iconCls of clicked icon is used.
 * In other words, action identifier === iconCls.
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2008, by Ing. Jozef Sakáloš
 * @date      22. March 2008
 * @version   1.0
 * @revision  $Id: Ext.ux.grid.RowActions.js 747 2009-09-03 23:30:52Z jozo $
 *
 * @license Ext.ux.grid.RowActions is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     29961
 * @demo      http://rowactions.extjs.eu
 * @download  
 * <ul>
 * <li><a href="http://rowactions.extjs.eu/rowactions.tar.bz2">rowactions.tar.bz2</a></li>
 * <li><a href="http://rowactions.extjs.eu/rowactions.tar.gz">rowactions.tar.gz</a></li>
 * <li><a href="http://rowactions.extjs.eu/rowactions.zip">rowactions.zip</a></li>
 * </ul>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.grid');

// add RegExp.escape if it has not been already added
if('function' !== typeof RegExp.escape) {
	RegExp.escape = function(s) {
		if('string' !== typeof s) {
			return s;
		}
		// Note: if pasting from forum, precede ]/\ with backslash manually
		return s.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1');
	}; // eo function escape
}

/**
 * Creates new RowActions plugin
 * @constructor
 * @param {Object} config A config object
 */
Ext.ux.grid.RowActions = function(config) {
	Ext.apply(this, config);

	// {{{
	this.addEvents(
		/**
		 * @event beforeaction
		 * Fires before action event. Return false to cancel the subsequent action event.
		 * @param {Ext.grid.GridPanel} grid
		 * @param {Ext.data.Record} record Record corresponding to row clicked
		 * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
		 * @param {Integer} rowIndex Index of clicked grid row
		 * @param {Integer} colIndex Index of clicked grid column that contains all action icons
		 */
		 'beforeaction'
		/**
		 * @event action
		 * Fires when icon is clicked
		 * @param {Ext.grid.GridPanel} grid
		 * @param {Ext.data.Record} record Record corresponding to row clicked
		 * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
		 * @param {Integer} rowIndex Index of clicked grid row
		 * @param {Integer} colIndex Index of clicked grid column that contains all action icons
		 */
		,'action'
		/**
		 * @event beforegroupaction
		 * Fires before group action event. Return false to cancel the subsequent groupaction event.
		 * @param {Ext.grid.GridPanel} grid
		 * @param {Array} records Array of records in this group
		 * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
		 * @param {String} groupId Identifies the group clicked
		 */
		,'beforegroupaction'
		/**
		 * @event groupaction
		 * Fires when icon in a group header is clicked
		 * @param {Ext.grid.GridPanel} grid
		 * @param {Array} records Array of records in this group
		 * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
		 * @param {String} groupId Identifies the group clicked
		 */
		,'groupaction'
	);
	// }}}

	// call parent
	Ext.ux.grid.RowActions.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, {

	// configuration options
	// {{{
	/**
	 * @cfg {Array} actions Mandatory. Array of action configuration objects. The action
	 * configuration object recognizes the following options:
	 * <ul class="list">
	 * <li style="list-style-position:outside">
	 *   {Function} <b>callback</b> (optional). Function to call if the action icon is clicked.
	 *   This function is called with same signature as action event and in its original scope.
	 *   If you need to call it in different scope or with another signature use 
	 *   createCallback or createDelegate functions. Works for statically defined actions. Use
	 *   callbacks configuration options for store bound actions.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {Function} <b>cb</b> Shortcut for callback.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>iconIndex</b> Optional, however either iconIndex or iconCls must be
	 *   configured. Field name of the field of the grid store record that contains
	 *   css class of the icon to show. If configured, shown icons can vary depending
	 *   of the value of this field.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>iconCls</b> CSS class of the icon to show. It is ignored if iconIndex is
	 *   configured. Use this if you want static icons that are not base on the values in the record.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {Boolean} <b>hide</b> Optional. True to hide this action while still have a space in 
	 *   the grid column allocated to it. IMO, it doesn't make too much sense, use hideIndex instead.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>hideIndex</b> Optional. Field name of the field of the grid store record that
	 *   contains hide flag (falsie [null, '', 0, false, undefined] to show, anything else to hide).
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>qtipIndex</b> Optional. Field name of the field of the grid store record that 
	 *   contains tooltip text. If configured, the tooltip texts are taken from the store.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>tooltip</b> Optional. Tooltip text to use as icon tooltip. It is ignored if 
	 *   qtipIndex is configured. Use this if you want static tooltips that are not taken from the store.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>qtip</b> Synonym for tooltip
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>textIndex</b> Optional. Field name of the field of the grids store record
	 *   that contains text to display on the right side of the icon. If configured, the text
	 *   shown is taken from record.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>text</b> Optional. Text to display on the right side of the icon. Use this
	 *   if you want static text that are not taken from record. Ignored if textIndex is set.
	 * </li>
	 * <li style="list-style-position:outside">
	 *   {String} <b>style</b> Optional. Style to apply to action icon container.
	 * </li>
	 * </ul>
	 */

	/**
	 * @cfg {String} actionEvent Event to trigger actions, e.g. click, dblclick, mouseover (defaults to 'click')
	 */
	 actionEvent:'click'
	/**
	 * @cfg {Boolean} autoWidth true to calculate field width for iconic actions only (defaults to true).
	 * If true, the width is calculated as {@link #widthSlope} * number of actions + {@link #widthIntercept}.
	 */
	,autoWidth:true

	/**
	 * @cfg {String} dataIndex - Do not touch!
	 * @private
	 */
	,dataIndex:''

	/**
	 * @cfg {Boolean} editable - Do not touch!
	 * Must be false to prevent errors in editable grids
	 */
	,editable:false

	/**
	 * @cfg {Array} groupActions Array of action to use for group headers of grouping grids.
	 * These actions support static icons, texts and tooltips same way as {@link #actions}. There is one
	 * more action config option recognized:
	 * <ul class="list">
	 * <li style="list-style-position:outside">
	 *   {String} <b>align</b> Set it to 'left' to place action icon next to the group header text.
	 *   (defaults to undefined = icons are placed at the right side of the group header.
	 * </li>
	 * </ul>
	 */

	/**
	 * @cfg {Object} callbacks iconCls keyed object that contains callback functions. For example:
	 * <pre>
	 * callbacks:{
	 * &nbsp;    'ra-icon-open':function(...) {...}
	 * &nbsp;   ,'ra-icon-save-ok':function(...) {...}
	 * }
	 * </pre>
	 */

	/**
	 * @cfg {String} header Actions column header
	 */
	,header:''

	/**
	 * @cfg {Boolean} isColumn
	 * Tell ColumnModel that we are column. Do not touch!
	 * @private
	 */
	,isColumn:true

	/**
	 * @cfg {Boolean} keepSelection
	 * Set it to true if you do not want action clicks to affect selected row(s) (defaults to false).
	 * By default, when user clicks an action icon the clicked row is selected and the action events are fired.
	 * If this option is true then the current selection is not affected, only the action events are fired.
	 */
	,keepSelection:false

	/**
	 * @cfg {Boolean} menuDisabled No sense to display header menu for this column
	 * @private
	 */
	,menuDisabled:true

	/**
	 * @cfg {Boolean} sortable Usually it has no sense to sort by this column
	 * @private
	 */
	,sortable:false

	/**
	 * @cfg {String} tplGroup Template for group actions
	 * @private
	 */
	,tplGroup:
		 '<tpl for="actions">'
		+'<div class="ux-grow-action-item<tpl if="\'right\'===align"> ux-action-right</tpl> '
		+'{cls}" style="{style}" qtip="{qtip}">{text}</div>'
		+'</tpl>'

	/**
	 * @cfg {String} tplRow Template for row actions
	 * @private
	 */
	,tplRow:
		 '<div class="ux-row-action">'
		+'<tpl for="actions">'
		+'<div class="ux-row-action-item {cls} <tpl if="text">'
		+'ux-row-action-text</tpl>" style="{hide}{style}" qtip="{qtip}">'
		+'<tpl if="text"><span qtip="{qtip}">{text}</span></tpl></div>'
		+'</tpl>'
		+'</div>'

	/**
	 * @cfg {String} hideMode How to hide hidden icons. Valid values are: 'visibility' and 'display' 
	 * (defaluts to 'visibility'). If the mode is visibility the hidden icon is not visible but there
	 * is still blank space occupied by the icon. In display mode, the visible icons are shifted taking
	 * the space of the hidden icon.
	 */
	,hideMode:'visibility'

	/**
	 * @cfg {Number} widthIntercept Constant used for auto-width calculation (defaults to 4).
	 * See {@link #autoWidth} for explanation.
	 */
	,widthIntercept:4

	/**
	 * @cfg {Number} widthSlope Constant used for auto-width calculation (defaults to 21).
	 * See {@link #autoWidth} for explanation.
	 */
	,widthSlope:21
	// }}}

	// methods
	// {{{
	/**
	 * Init function
	 * @param {Ext.grid.GridPanel} grid Grid this plugin is in
	 */
	,init:function(grid) {
		this.grid = grid;
		
		// the actions column must have an id for Ext 3.x
		this.id = this.id || Ext.id();

		// for Ext 3.x compatibility
		var lookup = grid.getColumnModel().lookup;
		delete(lookup[undefined]);
		lookup[this.id] = this;

		// {{{
		// setup template
		if(!this.tpl) {
			this.tpl = this.processActions(this.actions);

		} // eo template setup
		// }}}

		// calculate width
		if(this.autoWidth) {
			this.width =  this.widthSlope * this.actions.length + this.widthIntercept;
			this.fixed = true;
		}

		// body click handler
		var view = grid.getView();
		var cfg = {scope:this};
		cfg[this.actionEvent] = this.onClick;
		grid.afterRender = grid.afterRender.createSequence(function() {
			view.mainBody.on(cfg);
			grid.on('destroy', this.purgeListeners, this);
		}, this);

		// setup renderer
		if(!this.renderer) {
			this.renderer = function(value, cell, record, row, col, store) {
				cell.css += (cell.css ? ' ' : '') + 'ux-row-action-cell';
				return this.tpl.apply(this.getData(value, cell, record, row, col, store));
			}.createDelegate(this);
		}

		// actions in grouping grids support
		if(view.groupTextTpl && this.groupActions) {
			view.interceptMouse = view.interceptMouse.createInterceptor(function(e) {
				if(e.getTarget('.ux-grow-action-item')) {
					return false;
				}
			});
			view.groupTextTpl = 
				 '<div class="ux-grow-action-text">' + view.groupTextTpl +'</div>' 
				+this.processActions(this.groupActions, this.tplGroup).apply()
			;
		}

		// cancel click
		if(true === this.keepSelection) {
			grid.processEvent = grid.processEvent.createInterceptor(function(name, e) {
				if('mousedown' === name) {
					return !this.getAction(e);
				}
			}, this);
		}
		
	} // eo function init
	// }}}
	// {{{
	/**
	 * Returns data to apply to template. Override this if needed.
	 * @param {Mixed} value 
	 * @param {Object} cell object to set some attributes of the grid cell
	 * @param {Ext.data.Record} record from which the data is extracted
	 * @param {Number} row row index
	 * @param {Number} col col index
	 * @param {Ext.data.Store} store object from which the record is extracted
	 * @return {Object} data to apply to template
	 */
	,getData:function(value, cell, record, row, col, store) {
		return record.data || {};
	} // eo function getData
	// }}}
	// {{{
	/**
	 * Processes actions configs and returns template.
	 * @param {Array} actions
	 * @param {String} template Optional. Template to use for one action item.
	 * @return {String}
	 * @private
	 */
	,processActions:function(actions, template) {
		var acts = [];

		// actions loop
		Ext.each(actions, function(a, i) {
			// save callback
			if(a.iconCls && 'function' === typeof (a.callback || a.cb)) {
				this.callbacks = this.callbacks || {};
				this.callbacks[a.iconCls] = a.callback || a.cb;
			}

			// data for intermediate template
			var o = {
				 cls:a.iconIndex ? '{' + a.iconIndex + '}' : (a.iconCls ? a.iconCls : '')
				,qtip:a.qtipIndex ? '{' + a.qtipIndex + '}' : (a.tooltip || a.qtip ? a.tooltip || a.qtip : '')
				,text:a.textIndex ? '{' + a.textIndex + '}' : (a.text ? a.text : '')
				,hide:a.hideIndex 
					? '<tpl if="' + a.hideIndex + '">' 
						+ ('display' === this.hideMode ? 'display:none' :'visibility:hidden') + ';</tpl>' 
					: (a.hide ? ('display' === this.hideMode ? 'display:none' :'visibility:hidden;') : '')
				,align:a.align || 'right'
				,style:a.style ? a.style : ''
			};
			acts.push(o);

		}, this); // eo actions loop

		var xt = new Ext.XTemplate(template || this.tplRow);
		return new Ext.XTemplate(xt.apply({actions:acts}));

	} // eo function processActions
	// }}}
	,getAction:function(e) {
		var action = false;
		var t = e.getTarget('.ux-row-action-item');
		if(t) {
			action = t.className.replace(/ux-row-action-item /, '');
			if(action) {
				action = action.replace(/ ux-row-action-text/, '');
				action = action.trim();
			}
		}
		return action;
	} // eo function getAction
	// {{{
	/**
	 * Grid body actionEvent event handler
	 * @private
	 */
	,onClick:function(e, target) {

		var view = this.grid.getView();

		// handle row action click
		var row = e.getTarget('.x-grid3-row');
		var col = view.findCellIndex(target.parentNode.parentNode);
		var action = this.getAction(e);

//		var t = e.getTarget('.ux-row-action-item');
//		if(t) {
//			action = this.getAction(t);
//			action = t.className.replace(/ux-row-action-item /, '');
//			if(action) {
//				action = action.replace(/ ux-row-action-text/, '');
//				action = action.trim();
//			}
//		}
		if(false !== row && false !== col && false !== action) {
			var record = this.grid.store.getAt(row.rowIndex);

			// call callback if any
			if(this.callbacks && 'function' === typeof this.callbacks[action]) {
				this.callbacks[action](this.grid, record, action, row.rowIndex, col);
			}

			// fire events
			if(true !== this.eventsSuspended && false === this.fireEvent('beforeaction', this.grid, record, action, row.rowIndex, col)) {
				return;
			}
			else if(true !== this.eventsSuspended) {
				this.fireEvent('action', this.grid, record, action, row.rowIndex, col);
			}

		}

		// handle group action click
		t = e.getTarget('.ux-grow-action-item');
		if(t) {
			// get groupId
			var group = view.findGroup(target);
			var groupId = group ? group.id.replace(/ext-gen[0-9]+-gp-/, '') : null;

			// get matching records
			var records;
			if(groupId) {
				var re = new RegExp(RegExp.escape(groupId));
				records = this.grid.store.queryBy(function(r) {
					return r._groupId.match(re);
				});
				records = records ? records.items : [];
			}
			action = t.className.replace(/ux-grow-action-item (ux-action-right )*/, '');

			// call callback if any
			if('function' === typeof this.callbacks[action]) {
				this.callbacks[action](this.grid, records, action, groupId);
			}

			// fire events
			if(true !== this.eventsSuspended && false === this.fireEvent('beforegroupaction', this.grid, records, action, groupId)) {
				return false;
			}
			this.fireEvent('groupaction', this.grid, records, action, groupId);
		}
	} // eo function onClick
	// }}}

});

// registre xtype
Ext.reg('rowactions', Ext.ux.grid.RowActions);

// eof

// vim: ts=4:sw=4:nu:fdc=2:nospell
/*global Ext */
/**
 * @class Ext.ux.form.XCheckbox
 * @extends Ext.form.Checkbox
 *
 * A nicer checkbox always submitting configurable values
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2008, Ing. Jozef Sakáloš
 * @version   2.0
 * @date      10. February 2008
 * @revision  $Id: Ext.ux.form.XCheckbox.js 589 2009-02-21 23:30:18Z jozo $
 *
 * @license Ext.ux.form.XCheckbox is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     25924
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.form');

/**
 * Creates new XCheckbox
 * @constructor
 * @param {Object} config A config object
 */
Ext.ux.form.XCheckbox = Ext.extend(Ext.form.Checkbox, {
	/**
	 * @cfg {String} submitOffValue Value submitted if checkbox is unchecked (defaults to "false")
	 */
	 submitOffValue:'false'

	/**
	 * @cfg {String} submitOnValue Value submitted if checkbox is checked (defaults to "true")
	 */
	,submitOnValue:'true'

	,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) {
		v = this.convertValue(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 = this.convertValue(v);
		if(this.hiddenField) {
			this.hiddenField.dom.value = v ? this.submitOnValue : this.submitOffValue;
			this.hiddenField.dom.name = v ? '' : this.el.dom.name;
		}
	} // eo function updateHidden

	/**
	 * Converts value to boolean
	 * @private
	 */
	,convertValue:function(v) {
		return (v === true || v === 'true' || v == 1 || v === this.submitOnValue || String(v).toLowerCase() === 'on');
	} // eo function convertValue

}); // eo extend

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

// eof

// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
 * @class   Ext.ux.tree.CheckTreePanel
 * @extends Ext.tree.TreePanel
 *
 * <p>
 * I've taken Condor's work from 
 * <a href="http://extjs.com/forum/showthread.php?t=44369">http://extjs.com/forum/showthread.php?t=44369</a>,
 * removed tri-state functionality and put all files into one extension.
 * </p>
 * <p>
 * CheckTreePanel has been designed to fully load nodes in one shot using
 * nested "children" array. 
 * </p>
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 * @revision $Id: Ext.ux.tree.CheckTreePanel.js 593 2009-02-24 10:17:11Z jozo $
 *
 * @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum    56875
 * @demo     http://checktree.extjs.eu
 * @download 
 * <ul>
 * <li><a href="http://checktree.extjs.eu/checktree.tar.bz2">checktree.tar.bz2</a></li>
 * <li><a href="http://checktree.extjs.eu/checktree.tar.gz">checktree.tar.gz</a></li>
 * <li><a href="http://checktree.extjs.eu/checktree.zip">checktree.zip</a></li>
 * </ul>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.tree');

// {{{
/**
 * Creates new CheckTreePanel
 * @constructor
 * @param {Object} config The configuration object
 */
Ext.ux.tree.CheckTreePanel = Ext.extend(Ext.tree.TreePanel, {

	// {{{
	// default config
	/**
	 * @cfg {String} bubbleCheck 
	 * Check/uncheck also parent nodes. Valid values: none, checked, unchecked, all. Defaults to 'checked'.
	 */
	 bubbleCheck:'checked' 

	/**
	 * @cfg {String} cascadeCheck 
	 * Check/uncheck also child nodes. Valid values: none, checked, unchecked, all. Defaults to 'unchecked'.
	 */
	,cascadeCheck:'unchecked'

	/**
	 * @cfg {Boolean} deepestOnly 
	 * Set this to true for getValue to return only deepest checked node ids. Defaults to false.
	 */
	,deepestOnly:false

	/**
	 * @cfg {Boolean} expandOnCheck 
	 * Expand the node on check if true. Defaults to true;
	 */
	,expandOnCheck:true

	/**
	 * @cfg {Boolean/Object} filter
	 * Set it to false to not create tree filter or set a custom filter. Defaults to true.
	 */
	,filter:true

	/**
	 * @cfg {String} separator
	 * Separator for convertValue function. Defaults to ',' (comma).
	 */
	,separator:','

	/**
	 * @cfg {String} cls
	 * Class to add to CheckTreePanel. A suitable css file must be included. Defaults to 'ux-checktree'.
	 */
	,cls:'ux-checktree'

	/**
	 * @cfg {Object} baseAttrs
	 * baseAttrs for loader. Defaults to {} (empty object).
	 */
	,baseAttrs:{}
	// }}}
	// {{{
	,initComponent:function() {

		// use our event model
		this.eventModel = new Ext.ux.tree.CheckTreeEventModel(this);

		// call parent initComponent
		Ext.ux.tree.CheckTreePanel.superclass.initComponent.apply(this, arguments);

		// pass this.baseAttrs and uiProvider down the line
		var baseAttrs = Ext.apply({uiProvider:Ext.ux.tree.CheckTreeNodeUI}, this.baseAttrs);
		Ext.applyIf(this.loader, {baseAttrs:baseAttrs, preloadChildren:true});

		// make sure that nodes are deeply preloaded
		if(true === this.loader.preloadChildren) {
			this.loader.on('load', function(loader, node) {
				node.cascade(function(n) {
					loader.doPreload(n);
					n.loaded = true;
				});
			});
		}

		// create tree filter
		if(true === this.filter) {
			var Filter = Ext.ux.tree.TreeFilterX ? Ext.ux.tree.TreeFilterX : Ext.tree.TreeFilter;
			this.filter = new Filter(this, {autoClear:true});
		}

	} // eo function initComponent
	// }}}
	// {{{
	/*
	 * get "value" (array of checked nodes ids)
	 * @return {Array} Array of chcecked nodes ids
	 */
	,getValue:function() {
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push(n.id);
				}
			}
		}, this);
		return a;
	} // eo function getValue
	// {{{
	/**
	 * Helper function for getValue
	 * @param {Ext.tree.TreeNode} node
	 * @return {Boolean} true if node has a child checked, false otherwise
	 * @private
	 */
	,isChildChecked:function(node) {
		var checked = false;
		Ext.each(node.childNodes, function(child) {
			if(child.attributes.checked) {
				checked = true;
			}
		});
		return checked;
	} // eo function isChildChecked
	// }}}
	// }}}
	// {{{
	/*
	 * clears "value", unchecks all nodes
	 * @return {Ext.ux.tree.CheckTreePanel} this
	 */
	,clearValue:function() {
		this.root.cascade(function(n) {
			var ui = n.getUI();
			if(ui && ui.setChecked) {
				ui.setChecked(false);
			}
		});
		this.value = [];
		return this;
	} // eo function clearValue
	// }}}
	// {{{
	/*
	 * converts passed value to array
	 * @param {Mixed} val variable number of arguments, e.g. convertValue('1,8,7', 3, [9,4])
	 * @return {Array} converted value
	 */
	,convertValue:function(val) {
		// init return array
		var a = [];

		// calls itself recursively if necessary
		if(1 < arguments.length) {
			for(var i = 0; i < arguments.length; i++) {
				a.push(this.convertValue(arguments[i]));
			}
		}

		// nothing to do for arrays
		else if(Ext.isArray(val)) {
			a = val;
		}

		// just push numbers
		else if('number' === typeof val) {
			a.push(val);
		}

		// split strings
		else if('string' === typeof val) {
			a = val.split(this.separator);
		}

		return a;
	} // eo function convertValue
	// }}}
	// {{{
	/*
	 * Set nodes checked/unchecked
	 * @param {Mixed} val variable number of arguments, e.g. setValue('1,8,7', 3, [9,4])
	 * @return {Array} value. Array of checked nodes
	 */
	,setValue:function(val) {

		// uncheck all first
		this.clearValue();

		// process arguments
		this.value = this.convertValue.apply(this, arguments);

		// check nodes
		Ext.each(this.value, function(id) {
			var n = this.getNodeById(id);
			if(n) {
				var ui = n.getUI();
				if(ui && ui.setChecked) {
					ui.setChecked(true);

					// expand checked nodes
					if(true === this.expandOnCheck) {
						n.bubbleExpand();
					}
				}
			}
		}, this);

		return this.value;
	} // eo function setValue
	// }}}
	// {{{
	/*
	 * arbitrary attribute of checked nodes (text by default) is joined and separated with this.separator
	 * @param {String} attr Attribute to serialize
	 * @return {String} serialized attr of checked nodes
	 */
	,serialize:function(attr) {
		attr = attr || 'text';
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push(n[attr]);
				}
			}
		}, this);
		return a.join(this.separator + ' ');
	} // eo function serialize
	// }}}
	// {{{
	/*
	 * alias for serialize function
	 * @param {String} attr Attribute to serialize
	 * @return {String} serialized attr of checked nodes
	 */
	,getText:function(attr) {
		return this.serialize(attr);
	} // eo function getText
	// }}}
	// {{{
	/**
	 * Creates hidden field if we're running in form for BasicForm::getValues to work
	 * @private
	 */
	,onRender:function() {
		Ext.ux.tree.CheckTreePanel.superclass.onRender.apply(this, arguments);
		if(true === this.isFormField) {
			this.hiddenField = this.body.createChild({
				tag:'input', type:'hidden', name:this.name || this.id
			}, undefined, true);
		}
	} // eo function onRender
	// }}}
	// {{{
	/**
	 * Updates hidden field if one exists on checkchange
	 * @private
	 */
	,updateHidden:function() {
		if(this.hiddenField) {
			this.hiddenField.value = this.getValue().join(this.separator);
		}
	} // eo function updateHidden
	// }}}

	// form field compatibility methods
	// todo: They could be made much more clever
	,clearInvalid:Ext.emptyFn
	,markInvalid:Ext.emptyFn
	,validate:function() {
		return true;
	}
	,isValid:function() {
		return true;
	}
	,getName:function() {
		return this.name || this.id || '';
	}

}) // eo extend

Ext.reg('checktreepanel', Ext.ux.tree.CheckTreePanel);
// }}}
// {{{
/**
 * @private
 * @ignore
 */
Ext.override(Ext.tree.TreeNode, {
	/**
	 * Expands all parent nodes of this node
	 * @private
	 */
	bubbleExpand:function() {
		var root = this.getOwnerTree().root;
		var branch = [];
		var p = this;
		while(p !== root) {
			p = p.parentNode;
			branch.push(p);
		}
		branch.reverse();
		Ext.each(branch, function(n) {
			n.expand(false, false);
		});
	}
});
// }}}
// {{{
/**
 * @class Ext.ux.tree.CheckTreeNodeUI
 * @extends Ext.tree.TreeNodeUI
 *
 * Adds checkbox to the tree node UI. This class is not intended
 * to be instantiated explicitly; it is used internally in CheckTreePanel.
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 *
 * @license Ext.ux.tree.CheckTreeNodeUI is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */
/**
 * @constructor
 * @Creates new CheckTreeNodeUI
 * @param {Ext.tree.TreeNode} node Node to create the UI for
 */
Ext.ux.tree.CheckTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {

	// {{{
	/**
	 * This is slightly adjusted original renderElements method
	 * @private
	 */
	renderElements:function(n, a, targetNode, bulkRender){
		
		this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() :'';
		var checked = n.attributes.checked;
		var href = a.href ? a.href : Ext.isGecko ? "" :"#";
        var buf = [
			 '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">'
			,'<span class="x-tree-node-indent">',this.indentMarkup,"</span>"
			,'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />'
			,'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" :""),(a.iconCls ? " "+a.iconCls :""),'" unselectable="on" />'
			,'<img src="'+this.emptyIcon+'" class="x-tree-checkbox'+(true === checked ? ' x-tree-node-checked' :'')+'" />'
			,'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" '
			,a.hrefTarget ? ' target="'+a.hrefTarget+'"' :"", '><span unselectable="on">',n.text,"</span></a></div>"
			,'<ul class="x-tree-node-ct" style="display:none;"></ul>'
			,"</li>"
		].join('');
		var nel;
		if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
			this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
		}else{
			this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
		}
		this.elNode = this.wrap.childNodes[0];
		this.ctNode = this.wrap.childNodes[1];
		var cs = this.elNode.childNodes;
		this.indentNode = cs[0];
		this.ecNode = cs[1];
		this.iconNode = cs[2];
		this.checkbox = cs[3];
		this.cbEl = Ext.get(this.checkbox);
		this.anchor = cs[4];
		this.textNode = cs[4].firstChild;
	} // eo function renderElements
	// }}}
	// {{{
	/**
	 * Sets iconCls
	 * @param {String} iconCls
	 * @private
	 */
	,setIconCls:function(iconCls) {
		Ext.fly(this.iconNode).set({cls:'x-tree-node-icon ' + iconCls});
	} // eo function setIconCls
	// }}}
	// {{{
	/**
	 * @return {Boolean} true if the node is checked, false otherwise
	 * @private
	 */
	,isChecked:function() {
		return this.node.attributes.checked === true;
	} // eo function isChecked
	// }}}
	// {{{
	/**
	 * Called when check changes
	 * @private
	 */
	,onCheckChange:function() {
		var checked = this.isChecked();
		var tree = this.node.getOwnerTree();
		var bubble = tree.bubbleCheck;
		var cascade = tree.cascadeCheck;

		if('all' === bubble || (checked && 'checked' === bubble) || (!checked && 'unchecked' === bubble)) {
			this.updateParent(checked);
		}
		if('all' === cascade || (checked && 'checked' === cascade) || (!checked && 'unchecked' === cascade)) {
			this.updateChildren(checked);
		}

		tree.updateHidden();
		this.fireEvent('checkchange', this.node, checked);
	} // eo function onCheckChange
	// }}}
	// {{{
	/**
	 * Sets node UI checked/unchecked
	 * @param {Boolean} checked true to set node checked, false to uncheck
	 * @return {Boolean} checked
	 */
	,setChecked:function(checked) {
		checked = true === checked ? checked : false;
		var cb = this.cbEl || false;
		if(cb) {
			true === checked ? cb.addClass('x-tree-node-checked') : cb.removeClass('x-tree-node-checked');
		}
		this.node.attributes.checked = checked;
		this.onCheckChange();
		return checked;
	} // eo function setChecked
	// }}}
	// {{{
	/**
	 * Toggles check
	 * @return {Boolean} value after toggle
	 */
	,toggleCheck:function() {
		var checked = !this.isChecked();
		this.setChecked(checked);
		return checked;
	} // eo function toggleCheck
	// }}}
	// {{{
	/**
	 * Sets parents checked/unchecked. Used if bubbleCheck is not 'none'
	 * @param {Boolean} checked
	 * @private
	 */
	,updateParent:function(checked) {
		var p = this.node.parentNode;
		var ui = p ? p.getUI() : false;
		
		if(ui && ui.setChecked) {
			ui.setChecked(checked);
		}
	} // eo function updateParent
	// }}}
	// {{{
	/**
	 * Sets children checked/unchecked. Used if cascadeCheck is not 'none'
	 * @param {Boolean} checked
	 * @private
	 */
	,updateChildren:function(checked) {
		this.node.eachChild(function(n) {
			var ui = n.getUI();
			if(ui && ui.setChecked) {
				ui.setChecked(checked);
			}
		});
	} // eo function updateChildren
	// }}}
	// {{{
	/**
	 * Checkbox click event handler
	 * @private
	 */
	,onCheckboxClick:function() {
		if(!this.disabled) {
			this.toggleCheck();
		}
	} // eo function onCheckboxClick
	// }}}
	// {{{
	/**
	 * Checkbox over event handler
	 * @private
	 */
	,onCheckboxOver:function() {
		this.addClass('x-tree-checkbox-over');
	} // eo function onCheckboxOver
	// }}}
	// {{{
	/**
	 * Checkbox out event handler
	 * @private
	 */
	,onCheckboxOut:function() {
		this.removeClass('x-tree-checkbox-over');
	} // eo function onCheckboxOut
	// }}}
	// {{{
	/**
	 * Mouse down on checkbox event handler
	 * @private
	 */
	,onCheckboxDown:function() {
		this.addClass('x-tree-checkbox-down');
	} // eo function onCheckboxDown
	// }}}
	// {{{
	/**
	 * Mouse up on checkbox event handler
	 * @private
	 */
	,onCheckboxUp:function() {
		this.removeClass('x-tree-checkbox-down');
	} // eo function onCheckboxUp
	// }}}
 
}); // eo extend
// }}}
// {{{
/**
 * @class   Ext.ux.tree.CheckTreeEventModel
 * @extends Ext.tree.TreeEventModel
 *
 * Tree event model suitable for use with CheckTreePanel.
 * This class is not intended to be instantiated explicitly by a user
 * but it is used internally by CheckTreePanel.
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 *
 * @license Ext.ux.tree.CheckTreeEventModel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */
/**
 * Create new CheckTreeEventModel
 * @constructor
 * @param {Ext.ux.tree.CheckTreePanel} tree The tree to apply this event model to
 */
Ext.ux.tree.CheckTreeEventModel = Ext.extend(Ext.tree.TreeEventModel, {
     initEvents:function(){
        var el = this.tree.getTreeEl();
        el.on('click', this.delegateClick, this);
        if(this.tree.trackMouseOver !== false){
            el.on('mouseover', this.delegateOver, this);
            el.on('mouseout', this.delegateOut, this);
        }
        el.on('mousedown', this.delegateDown, this);
        el.on('mouseup', this.delegateUp, this);
        el.on('dblclick', this.delegateDblClick, this);
        el.on('contextmenu', this.delegateContextMenu, this);
    }
	,delegateOver:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(this.lastEcOver){
            this.onIconOut(e, this.lastEcOver);
            delete this.lastEcOver;
        }
        if(this.lastCbOver){
            this.onCheckboxOut(e, this.lastCbOver);
            delete this.lastCbOver;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            this.lastEcOver = this.getNode(e);
            this.onIconOver(e, this.lastEcOver);
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            this.lastCbOver = this.getNode(e);
            this.onCheckboxOver(e, this.lastCbOver);
        }
        if(t = this.getNodeTarget(e)){
            this.onNodeOver(e, this.getNode(e));
        }
    }
	,delegateOut:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            var n = this.getNode(e);
            this.onIconOut(e, n);
            if(n == this.lastEcOver){
                delete this.lastEcOver;
            }
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            var n = this.getNode(e);
            this.onCheckboxOut(e, n);
            if(n == this.lastCbOver){
                delete this.lastCbOver;
            }
        }
        if((t = this.getNodeTarget(e)) && !e.within(t, true)){
            this.onNodeOut(e, this.getNode(e));
        }
    }
	,delegateDown:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-checkbox', 1)){
            this.onCheckboxDown(e, this.getNode(e));
        }
    }
	,delegateUp:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-checkbox', 1)){
            this.onCheckboxUp(e, this.getNode(e));
        }
    }
	,delegateOut:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            var n = this.getNode(e);
            this.onIconOut(e, n);
            if(n == this.lastEcOver){
                delete this.lastEcOver;
            }
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            var n = this.getNode(e);
            this.onCheckboxOut(e, n);
            if(n == this.lastCbOver){
                delete this.lastCbOver;
            }
        }
        if((t = this.getNodeTarget(e)) && !e.within(t, true)){
            this.onNodeOut(e, this.getNode(e));
        }
    }
	,delegateClick:function(e, t){
		if(!this.beforeEvent(e)){
			return;
		}
		if(e.getTarget('.x-tree-checkbox', 1)){
			this.onCheckboxClick(e, this.getNode(e));
		}
		else if(e.getTarget('.x-tree-ec-icon', 1)){
			this.onIconClick(e, this.getNode(e));
		}
		else if(this.getNodeTarget(e)){
			this.onNodeClick(e, this.getNode(e));
		}
	}
	,onCheckboxClick:function(e, node){
		node.ui.onCheckboxClick();
	}
	,onCheckboxOver:function(e, node){
		node.ui.onCheckboxOver();
	}
	,onCheckboxOut:function(e, node){
		node.ui.onCheckboxOut();
	}
	,onCheckboxDown:function(e, node){
		node.ui.onCheckboxDown();
	}
	,onCheckboxUp:function(e, node){
		node.ui.onCheckboxUp();
	}
}); // eo extend
// }}}

// eof

// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
 * @class Ext.ux.form.DateTime
 * @extends Ext.form.Field
 *
 * DateTime field, combination of DateField and TimeField
 *
 * @author	  Ing. Jozef Sakáloš
 * @copyright (c) 2008, Ing. Jozef Sakáloš
 * @version   2.0
 * @revision  $Id: Ext.ux.form.DateTime.js 813 2010-01-29 23:32:36Z jozo $
 *
 * @license Ext.ux.form.DateTime is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum      22661
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.form');

/**
 * Creates new DateTime
 * @constructor
 * @param {Object} config A config object
 */
Ext.ux.form.DateTime = Ext.extend(Ext.form.Field, {
	/**
	 * @cfg {Function} dateValidator A custom validation function to be called during date field
	 * validation (defaults to null)
	 */
	 dateValidator:null
	/**
	 * @cfg {String/Object} defaultAutoCreate DomHelper element spec
	 * Let superclass to create hidden field instead of textbox. Hidden will be submittend to server
	 */
	,defaultAutoCreate:{tag:'input', type:'hidden'}
	/**
	 * @cfg {String} dtSeparator Date - Time separator. Used to split date and time (defaults to ' ' (space))
	 */
	,dtSeparator:' '
	/**
	 * @cfg {String} hiddenFormat Format of datetime used to store value in hidden field
	 * and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql format)
	 */
	,hiddenFormat:'Y-m-d H:i:s'
	/**
	 * @cfg {Boolean} otherToNow Set other field to now() if not explicly filled in (defaults to true)
	 */
	,otherToNow:true
	/**
	 * @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty value.
	 * If it is true then setValue() sets value of field to current date and time (defaults to false)
	 */
	/**
	 * @cfg {String} timePosition Where the time field should be rendered. 'right' is suitable for forms
	 * and 'below' is suitable if the field is used as the grid editor (defaults to 'right')
	 */
	,timePosition:'right' // valid values:'below', 'right'
	/**
	 * @cfg {Function} timeValidator A custom validation function to be called during time field
	 * validation (defaults to null)
	 */
	,timeValidator:null
	/**
	 * @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
	 */
	,timeWidth:100
	/**
	 * @cfg {String} dateFormat Format of DateField. Can be localized. (defaults to 'm/y/d')
	 */
	,dateFormat:'m/d/y'
	/**
	 * @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults to 'g:i A')
	 */
	,timeFormat:'g:i A'
	/**
	 * @cfg {Object} dateConfig Config for DateField constructor.
	 */
	/**
	 * @cfg {Object} timeConfig Config for TimeField constructor.
	 */

	// {{{
	/**
	 * @private
	 * creates DateField and TimeField and installs the necessary event handlers
	 */
	,initComponent:function() {
		// call parent initComponent
		Ext.ux.form.DateTime.superclass.initComponent.call(this);

		// create DateField
		var dateConfig = Ext.apply({}, {
			 id:this.id + '-date'
			,format:this.dateFormat || Ext.form.DateField.prototype.format
			,width:this.timeWidth
			,selectOnFocus:this.selectOnFocus
			,validator:this.dateValidator
			,listeners:{
				  blur:{scope:this, fn:this.onBlur}
				 ,focus:{scope:this, fn:this.onFocus}
			}
		}, this.dateConfig);
		this.df = new Ext.form.DateField(dateConfig);
		this.df.ownerCt = this;
		delete(this.dateFormat);

		// create TimeField
		var timeConfig = Ext.apply({}, {
			 id:this.id + '-time'
			,format:this.timeFormat || Ext.form.TimeField.prototype.format
			,width:this.timeWidth
			,selectOnFocus:this.selectOnFocus
			,validator:this.timeValidator
			,listeners:{
				  blur:{scope:this, fn:this.onBlur}
				 ,focus:{scope:this, fn:this.onFocus}
			}
		}, this.timeConfig);
		this.tf = new Ext.form.TimeField(timeConfig);
		this.tf.ownerCt = this;
		delete(this.timeFormat);

		// relay events
		this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
		this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);

		this.on('specialkey', this.onSpecialKey, this);

	} // eo function initComponent
	// }}}
	// {{{
	/**
	 * @private
	 * Renders underlying DateField and TimeField and provides a workaround for side error icon bug
	 */
	,onRender:function(ct, position) {
		// don't run more than once
		if(this.isRendered) {
			return;
		}

		// render underlying hidden field
		Ext.ux.form.DateTime.superclass.onRender.call(this, ct, position);

		// render DateField and TimeField
		// create bounding table
		var t;
		if('below' === this.timePosition || 'bellow' === this.timePosition) {
			t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
				 {tag:'tr',children:[{tag:'td', style:'padding-bottom:1px', cls:'ux-datetime-date'}]}
				,{tag:'tr',children:[{tag:'td', cls:'ux-datetime-time'}]}
			]}, true);
		}
		else {
			t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
				{tag:'tr',children:[
					{tag:'td',style:'padding-right:4px', cls:'ux-datetime-date'},{tag:'td', cls:'ux-datetime-time'}
				]}
			]}, true);
		}

		this.tableEl = t;
		this.wrap = t.wrap({cls:'x-form-field-wrap'});
//		this.wrap = t.wrap();
        this.wrap.on("mousedown", this.onMouseDown, this, {delay:10});

		// render DateField & TimeField
		this.df.render(t.child('td.ux-datetime-date'));
		this.tf.render(t.child('td.ux-datetime-time'));

		// workaround for IE trigger misalignment bug
		// see http://extjs.com/forum/showthread.php?p=341075#post341075
//		if(Ext.isIE && Ext.isStrict) {
//			t.select('input').applyStyles({top:0});
//		}

		this.df.el.swallowEvent(['keydown', 'keypress']);
		this.tf.el.swallowEvent(['keydown', 'keypress']);

		// create icon for side invalid errorIcon
		if('side' === this.msgTarget) {
			var elp = this.el.findParent('.x-form-element', 10, true);
			if(elp) {
				this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
			}

			var o = {
				 errorIcon:this.errorIcon
				,msgTarget:'side'
				,alignErrorIcon:this.alignErrorIcon.createDelegate(this)
			};
			Ext.apply(this.df, o);
			Ext.apply(this.tf, o);
//			this.df.errorIcon = this.errorIcon;
//			this.tf.errorIcon = this.errorIcon;
		}

		// setup name for submit
		this.el.dom.name = this.hiddenName || this.name || this.id;

		// prevent helper fields from being submitted
		this.df.el.dom.removeAttribute("name");
		this.tf.el.dom.removeAttribute("name");

		// we're rendered flag
		this.isRendered = true;

		// update hidden field
		this.updateHidden();

	} // eo function onRender
	// }}}
	// {{{
	/**
	 * @private
	 */
    ,adjustSize:Ext.BoxComponent.prototype.adjustSize
	// }}}
	// {{{
	/**
	 * @private
	 */
	,alignErrorIcon:function() {
        this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
	}
	// }}}
	// {{{
	/**
	 * @private initializes internal dateValue
	 */
	,initDateValue:function() {
		this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
	}
	// }}}
	// {{{
    /**
     * Calls clearInvalid on the DateField and TimeField
     */
    ,clearInvalid:function(){
        this.df.clearInvalid();
        this.tf.clearInvalid();
    } // eo function clearInvalid
    // }}}
	// {{{
    /**
     * Calls markInvalid on both DateField and TimeField
	 * @param {String} msg Invalid message to display
     */
    ,markInvalid:function(msg){
        this.df.markInvalid(msg);
        this.tf.markInvalid(msg);
    } // eo function markInvalid
    // }}}
	// {{{
	/**
	 * @private
	 * called from Component::destroy. 
	 * Destroys all elements and removes all listeners we've created.
	 */
	,beforeDestroy:function() {
		if(this.isRendered) {
//			this.removeAllListeners();
			this.wrap.removeAllListeners();
			this.wrap.remove();
			this.tableEl.remove();
			this.df.destroy();
			this.tf.destroy();
		}
	} // eo function beforeDestroy
	// }}}
	// {{{
    /**
     * Disable this component.
     * @return {Ext.Component} this
     */
    ,disable:function() {
		if(this.isRendered) {
			this.df.disabled = this.disabled;
			this.df.onDisable();
			this.tf.onDisable();
		}
		this.disabled = true;
		this.df.disabled = true;
		this.tf.disabled = true;
        this.fireEvent("disable", this);
        return this;
    } // eo function disable
	// }}}
	// {{{
    /**
     * Enable this component.
     * @return {Ext.Component} this
     */
    ,enable:function() {
        if(this.rendered){
			this.df.onEnable();
			this.tf.onEnable();
        }
        this.disabled = false;
		this.df.disabled = false;
		this.tf.disabled = false;
        this.fireEvent("enable", this);
        return this;
    } // eo function enable
	// }}}
	// {{{
	/**
	 * @private Focus date filed
	 */
	,focus:function() {
		this.df.focus();
	} // eo function focus
	// }}}
	// {{{
	/**
	 * @private
	 */
	,getPositionEl:function() {
		return this.wrap;
	}
	// }}}
	// {{{
	/**
	 * @private
	 */
	,getResizeEl:function() {
		return this.wrap;
	}
	// }}}
	// {{{
	/**
	 * @return {Date/String} Returns value of this field
	 */
	,getValue:function() {
		// create new instance of date
		return this.dateValue ? new Date(this.dateValue) : '';
	} // eo function getValue
	// }}}
	// {{{
	/**
	 * @return {Boolean} true = valid, false = invalid
	 * @private Calls isValid methods of underlying DateField and TimeField and returns the result
	 */
	,isValid:function() {
		return this.df.isValid() && this.tf.isValid();
	} // eo function isValid
	// }}}
    // {{{
    /**
     * Returns true if this component is visible
     * @return {boolean} 
     */
    ,isVisible : function(){
        return this.df.rendered && this.df.getActionEl().isVisible();
    } // eo function isVisible
	// }}}
	// {{{
	/** 
	 * @private Handles blur event
	 */
	,onBlur:function(f) {
		// called by both DateField and TimeField blur events

		// revert focus to previous field if clicked in between
		if(this.wrapClick) {
			f.focus();
			this.wrapClick = false;
		}

		// update underlying value
		if(f === this.df) {
			this.updateDate();
		}
		else {
			this.updateTime();
		}
		this.updateHidden();

		this.validate();

		// fire events later
		(function() {
			if(!this.df.hasFocus && !this.tf.hasFocus) {
				var v = this.getValue();
				if(String(v) !== String(this.startValue)) {
					this.fireEvent("change", this, v, this.startValue);
				}
				this.hasFocus = false;
				this.fireEvent('blur', this);
			}
		}).defer(100, this);

	} // eo function onBlur
	// }}}
	// {{{
	/**
	 * @private Handles focus event
	 */
	,onFocus:function() {
        if(!this.hasFocus){
            this.hasFocus = true;
            this.startValue = this.getValue();
            this.fireEvent("focus", this);
        }
	}
	// }}}
	// {{{
	/**
	 * @private Just to prevent blur event when clicked in the middle of fields
	 */
	,onMouseDown:function(e) {
		if(!this.disabled) {
			this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
		}
	}
	// }}}
	// {{{
	/**
	 * @private
	 * Handles Tab and Shift-Tab events
	 */
	,onSpecialKey:function(t, e) {
		var key = e.getKey();
		if(key === e.TAB) {
			if(t === this.df && !e.shiftKey) {
				e.stopEvent();
				this.tf.focus();
			}
			if(t === this.tf && e.shiftKey) {
				e.stopEvent();
				this.df.focus();
			}
			this.updateValue();
		}
		// otherwise it misbehaves in editor grid
		if(key === e.ENTER) {
			this.updateValue();
		}

	} // eo function onSpecialKey
	// }}}
	// {{{
	/**
	 * Resets the current field value to the originally loaded value 
	 * and clears any validation messages. See Ext.form.BasicForm.trackResetOnLoad
	 */
	,reset:function() {
		this.df.setValue(this.originalValue);
		this.tf.setValue(this.originalValue);
	} // eo function reset
	// }}}
	// {{{
	/**
	 * @private Sets the value of DateField
	 */
	,setDate:function(date) {
		this.df.setValue(date);
	} // eo function setDate
	// }}}
	// {{{
	/** 
	 * @private Sets the value of TimeField
	 */
	,setTime:function(date) {
		this.tf.setValue(date);
	} // eo function setTime
	// }}}
	// {{{
	/**
	 * @private
	 * Sets correct sizes of underlying DateField and TimeField
	 * With workarounds for IE bugs
	 */
	,setSize:function(w, h) {
		if(!w) {
			return;
		}
		if('below' === this.timePosition) {
			this.df.setSize(w, h);
			this.tf.setSize(w, h);
			if(Ext.isIE) {
				this.df.el.up('td').setWidth(w);
				this.tf.el.up('td').setWidth(w);
			}
		}
		else {
			this.df.setSize(w - this.timeWidth - 4, h);
			this.tf.setSize(this.timeWidth, h);

			if(Ext.isIE) {
				this.df.el.up('td').setWidth(w - this.timeWidth - 4);
				this.tf.el.up('td').setWidth(this.timeWidth);
			}
		}
	} // eo function setSize
	// }}}
	// {{{
	/**
	 * @param {Mixed} val Value to set
	 * Sets the value of this field
	 */
	,setValue:function(val) {
		if(!val && true === this.emptyToNow) {
			this.setValue(new Date());
			return;
		}
		else if(!val) {
			this.setDate('');
			this.setTime('');
			this.updateValue();
			return;
		}
        if ('number' === typeof val) {
          val = new Date(val);
        }
        else if('string' === typeof val && this.hiddenFormat) {
			val = Date.parseDate(val, this.hiddenFormat);
        }
		val = val ? val : new Date(1970, 0 ,1, 0, 0, 0);
		var da;
		if(val instanceof Date) {
			this.setDate(val);
			this.setTime(val);
			this.dateValue = new Date(Ext.isIE ? val.getTime() : val);
		}
		else {
			da = val.split(this.dtSeparator);
			this.setDate(da[0]);
			if(da[1]) {
				if(da[2]) {
					// add am/pm part back to time
					da[1] += da[2];
				}
				this.setTime(da[1]);
			}
		}
		this.updateValue();
	} // eo function setValue
	// }}}
	// {{{
	/**
     * Hide or show this component by boolean
     * @return {Ext.Component} this
     */
    ,setVisible: function(visible){
        if(visible) {
            this.df.show();
            this.tf.show();
        }else{
            this.df.hide();
            this.tf.hide();
        }
        return this;
    } // eo function setVisible
    // }}}
	//{{{
	,show:function() {
		return this.setVisible(true);
	} // eo function show
	//}}}
	//{{{
	,hide:function() {
		return this.setVisible(false);
	} // eo function hide
	//}}}
	// {{{
	/**
	 * @private Updates the date part
	 */
	,updateDate:function() {

		var d = this.df.getValue();
		if(d) {
			if(!(this.dateValue instanceof Date)) {
				this.initDateValue();
				if(!this.tf.getValue()) {
					this.setTime(this.dateValue);
				}
			}
			this.dateValue.setMonth(0); // because of leap years
			this.dateValue.setFullYear(d.getFullYear());
			this.dateValue.setMonth(d.getMonth(), d.getDate());
//			this.dateValue.setDate(d.getDate());
		}
		else {
			this.dateValue = '';
			this.setTime('');
		}
	} // eo function updateDate
	// }}}
	// {{{
	/**
	 * @private
	 * Updates the time part
	 */
	,updateTime:function() {
		var t = this.tf.getValue();
		if(t && !(t instanceof Date)) {
			t = Date.parseDate(t, this.tf.format);
		}
		if(t && !this.df.getValue()) {
			this.initDateValue();
			this.setDate(this.dateValue);
		}
		if(this.dateValue instanceof Date) {
			if(t) {
				this.dateValue.setHours(t.getHours());
				this.dateValue.setMinutes(t.getMinutes());
				this.dateValue.setSeconds(t.getSeconds());
			}
			else {
				this.dateValue.setHours(0);
				this.dateValue.setMinutes(0);
				this.dateValue.setSeconds(0);
			}
		}
	} // eo function updateTime
	// }}}
	// {{{
	/**
	 * @private Updates the underlying hidden field value
	 */
	,updateHidden:function() {
		if(this.isRendered) {
			var value = this.dateValue instanceof Date ? this.dateValue.format(this.hiddenFormat) : '';
			this.el.dom.value = value;
		}
	}
	// }}}
	// {{{
	/**
	 * @private Updates all of Date, Time and Hidden
	 */
	,updateValue:function() {

		this.updateDate();
		this.updateTime();
		this.updateHidden();

		return;
	} // eo function updateValue
	// }}}
	// {{{
	/**
	 * @return {Boolean} true = valid, false = invalid
	 * calls validate methods of DateField and TimeField
	 */
	,validate:function() {
		return this.df.validate() && this.tf.validate();
	} // eo function validate
	// }}}
	// {{{
	/**
	 * Returns renderer suitable to render this field
	 * @param {Object} Column model config
	 */
	,renderer: function(field) {
		var format = field.editor.dateFormat || Ext.ux.form.DateTime.prototype.dateFormat;
		format += ' ' + (field.editor.timeFormat || Ext.ux.form.DateTime.prototype.timeFormat);
		var renderer = function(val) {
			var retval = Ext.util.Format.date(val, format);
			return retval;
		};
		return renderer;
	} // eo function renderer
	// }}}

}); // eo extend

// register xtype
Ext.reg('xdatetime', Ext.ux.form.DateTime);

// eof

// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
 * @class Ext.ux.grid.Search
 * @extends Ext.util.Observable
 *
 * Search plugin for Ext.grid.GridPanel, Ext.grid.EditorGrid ver. 2.x or subclasses of them
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2008, by Ing. Jozef Sakáloš
 * @date      <ul>
 * <li>17. January 2008<li>
 * <li>6. February 2009</li>
 * </ul>
 * @version   1.1.1
 * @revision  $Id: Ext.ux.grid.Search.js 798 2010-01-17 00:46:57Z jozo $
 *
 * @license Ext.ux.grid.Search is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     23615
 * @demo      http://gridsearch.extjs.eu
 * @download  
 * <ul>
 * <li><a href="http://gridsearch.extjs.eu/gridsearch.tar.bz2">gridsearch.tar.bz2</a></li>
 * <li><a href="http://gridsearch.extjs.eu/gridsearch.tar.gz">gridsearch.tar.gz</a></li>
 * <li><a href="http://gridsearch.extjs.eu/gridsearch.zip">gridsearch.zip</a></li>
 * </ul>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.grid');

// Check RegExp.escape dependency
if('function' !== typeof RegExp.escape) {
	throw('RegExp.escape function is missing. Include Ext.ux.util.js file.');
}

/**
 * Creates new Search plugin
 * @constructor
 * @param {Object} A config object
 */
Ext.ux.grid.Search = function(config) {
	Ext.apply(this, config);
	Ext.ux.grid.Search.superclass.constructor.call(this);
}; // eo constructor

Ext.extend(Ext.ux.grid.Search, Ext.util.Observable, {
	/**
	 * @cfg {Boolean} autoFocus Try to focus the input field on each store load if set to true (defaults to undefined)
	 */

	/**
	 * @cfg {String} searchText Text to display on menu button
	 */
	 searchText:'Search'

	/**
	 * @cfg {String} searchTipText Text to display as input tooltip. Set to '' for no tooltip
	 */ 
	,searchTipText:'Type a text to search and press Enter'

	/**
	 * @cfg {String} selectAllText Text to display on menu item that selects all fields
	 */
	,selectAllText:'Select All'

	/**
	 * @cfg {String} position Where to display the search controls. Valid values are top and bottom
	 * Corresponding toolbar has to exist at least with mimimum configuration tbar:[] for position:top or bbar:[]
	 * for position bottom. Plugin does NOT create any toolbar.(defaults to "bottom")
	 */
	,position:'bottom'

	/**
	 * @cfg {String} iconCls Icon class for menu button (defaults to "ra-icon-magnifier")
	 */
	,iconCls:'ra-icon-magnifier'

	/**
	 * @cfg {String/Array} checkIndexes Which indexes to check by default. Can be either 'all' for all indexes
	 * or array of dataIndex names, e.g. ['persFirstName', 'persLastName'] (defaults to "all")
	 */
	,checkIndexes:'all'

	/**
	 * @cfg {Array} disableIndexes Array of index names to disable (not show in the menu), e.g. ['persTitle', 'persTitle2']
	 * (defaults to [] - empty array)
	 */
	,disableIndexes:[]

	/**
	 * Field containing search text (read-only)
	 * @property field
	 * @type {Ext.form.TwinTriggerField}
	 */

	/**
	 * @cfg {String} dateFormat How to format date values. If undefined (the default) 
	 * date is formatted as configured in colummn model
	 */

	/**
	 * @cfg {Boolean} showSelectAll Select All item is shown in menu if true (defaults to true)
	 */
	,showSelectAll:true

	/**
	 * Menu containing the column module fields menu with checkboxes (read-only)
	 * @property menu
	 * @type {Ext.menu.Menu}
	 */

	/**
	 * @cfg {String} menuStyle Valid values are 'checkbox' and 'radio'. If menuStyle is radio
	 * then only one field can be searched at a time and selectAll is automatically switched off. 
	 * (defaults to "checkbox")
	 */
	,menuStyle:'checkbox'

	/**
	 * @cfg {Number} minChars Minimum characters to type before the request is made. If undefined (the default)
	 * the trigger field shows magnifier icon and you need to click it or press enter for search to start. If it
	 * is defined and greater than 0 then maginfier is not shown and search starts after minChars are typed.
	 * (defaults to undefined)
	 */

	/**
	 * @cfg {String} minCharsTipText Tooltip to display if minChars is > 1
	 */
	,minCharsTipText:'Type at least {0} characters'

	/**
	 * @cfg {String} mode Use 'remote' for remote stores or 'local' for local stores. If mode is local
	 * no data requests are sent to server the grid's store is filtered instead (defaults to "remote")
	 */
	,mode:'remote'

	/**
	 * @cfg {Array} readonlyIndexes Array of index names to disable (show in menu disabled), e.g. ['persTitle', 'persTitle2']
	 * (defaults to undefined)
	 */

	/**
	 * @cfg {Number} width Width of input field in pixels (defaults to 100)
	 */
	,width:100

	/**
	 * @cfg {String} xtype xtype is usually not used to instantiate this plugin but you have a chance to identify it
	 */
	,xtype:'gridsearch'

	/**
	 * @cfg {Object} paramNames Params name map (defaults to {fields:"fields", query:"query"}
	 */
	,paramNames: {
		 fields:'fields'
		,query:'query'
	}

	/**
	 * @cfg {String} shortcutKey Key to fucus the input field (defaults to r = Sea_r_ch). Empty string disables shortcut
	 */
	,shortcutKey:'r'

	/**
	 * @cfg {String} shortcutModifier Modifier for shortcutKey. Valid values: alt, ctrl, shift (defaults to "alt")
	 */
	,shortcutModifier:'alt'

	/**
	 * @cfg {String} align "left" or "right" (defaults to "left")
	 */

	/**
	 * @cfg {Number} minLength Force user to type this many character before he can make a search 
	 * (defaults to undefined)
	 */

	/**
	 * @cfg {Ext.Panel/String} toolbarContainer Panel (or id of the panel) which contains toolbar we want to render
	 * search controls to (defaults to this.grid, the grid this plugin is plugged-in into)
	 */
	
	// {{{
	/**
	 * @private
	 * @param {Ext.grid.GridPanel/Ext.grid.EditorGrid} grid reference to grid this plugin is used for
	 */
	,init:function(grid) {
		this.grid = grid;

		// setup toolbar container if id was given
		if('string' === typeof this.toolbarContainer) {
			this.toolbarContainer = Ext.getCmp(this.toolbarContainer);
		}

		// do our processing after grid render and reconfigure
		grid.onRender = grid.onRender.createSequence(this.onRender, this);
		grid.reconfigure = grid.reconfigure.createSequence(this.reconfigure, this);
	} // eo function init
	// }}}
	// {{{
	/**
	 * adds plugin controls to <b>existing</b> toolbar and calls reconfigure
	 * @private
	 */
	,onRender:function() {
		var panel = this.toolbarContainer || this.grid;
		var tb = 'bottom' === this.position ? panel.bottomToolbar : panel.topToolbar;
		// add menu
		this.menu = new Ext.menu.Menu();

		// handle position
		if('right' === this.align) {
			tb.addFill();
		}
		else {
			if(0 < tb.items.getCount()) {
				tb.addSeparator();
			}
		}

		// add menu button
		tb.add({
			 text:this.searchText
			,menu:this.menu
			,iconCls:this.iconCls
		});

		// add input field (TwinTriggerField in fact)
		this.field = new Ext.form.TwinTriggerField({
			 width:this.width
			,selectOnFocus:undefined === this.selectOnFocus ? true : this.selectOnFocus
			,trigger1Class:'x-form-clear-trigger'
			,trigger2Class:this.minChars ? 'x-hide-display' : 'x-form-search-trigger'
			,onTrigger1Click:this.onTriggerClear.createDelegate(this)
			,onTrigger2Click:this.minChars ? Ext.emptyFn : this.onTriggerSearch.createDelegate(this)
			,minLength:this.minLength
		});

		// install event handlers on input field
		this.field.on('render', function() {
			// register quick tip on the way to search
			
			/*
			if((undefined === this.minChars || 1 < this.minChars) && this.minCharsTipText) {
				Ext.QuickTips.register({
					 target:this.field.el
					,text:this.minChars ? String.format(this.minCharsTipText, this.minChars) : this.searchTipText
				});
			}
			*/
			
			

			if(this.minChars) {
				this.field.el.on({scope:this, buffer:300, keyup:this.onKeyUp});
			}

			// install key map
			var map = new Ext.KeyMap(this.field.el, [{
				 key:Ext.EventObject.ENTER
				,scope:this
				,fn:this.onTriggerSearch
			},{
				 key:Ext.EventObject.ESC
				,scope:this
				,fn:this.onTriggerClear
			}]);
			map.stopEvent = true;
		}, this, {single:true});

		tb.add(this.field);

		// re-layout the panel if the toolbar is outside
		if(panel !== this.grid) {
			this.toolbarContainer.doLayout();
		}

		// reconfigure
		this.reconfigure();

		// keyMap
		if(this.shortcutKey && this.shortcutModifier) {
			var shortcutEl = this.grid.getEl();
			var shortcutCfg = [{
				 key:this.shortcutKey
				,scope:this
				,stopEvent:true
				,fn:function() {
					this.field.focus();
				}
			}];
			shortcutCfg[0][this.shortcutModifier] = true;
			this.keymap = new Ext.KeyMap(shortcutEl, shortcutCfg);
		}

		if(true === this.autoFocus) {
			this.grid.store.on({scope:this, load:function(){this.field.focus();}});
		}

	} // eo function onRender
	// }}}
	// {{{
	/**
	 * field el keypup event handler. Triggers the search
	 * @private
	 */
	,onKeyUp:function(e, t, o) {

		// ignore special keys 
		if(e.isNavKeyPress()) {
			return;
		}

		var length = this.field.getValue().toString().length;
		if(0 === length || this.minChars <= length) {
			this.onTriggerSearch();
		}
	} // eo function onKeyUp
	// }}}
	// {{{
	/**
	 * Clear Trigger click handler
	 * @private 
	 */
	,onTriggerClear:function() {
		if(this.field.getValue()) {
			this.field.setValue('');
			this.field.focus();
			this.onTriggerSearch();
		}
	} // eo function onTriggerClear
	// }}}
	// {{{
	/**
	 * Search Trigger click handler (executes the search, local or remote)
	 * @private 
	 */
	,onTriggerSearch:function() {
		if(!this.field.isValid()) {
			return;
		}
		var val = this.field.getValue();
		var store = this.grid.store;

		// grid's store filter
		if('local' === this.mode) {
			store.clearFilter();
			if(val) {
				store.filterBy(function(r) {
					var retval = false;
					this.menu.items.each(function(item) {
						if(!item.checked || retval) {
							return;
						}
						var rv = r.get(item.dataIndex);
						rv = rv instanceof Date ? rv.format(this.dateFormat || r.fields.get(item.dataIndex).dateFormat) : rv;
						var re = new RegExp(RegExp.escape(val), 'gi');
						retval = re.test(rv);
					}, this);
					if(retval) {
						return true;
					}
					return retval;
				}, this);
			}
			else {
			}
		}
		// ask server to filter records
		else {
			// clear start (necessary if we have paging)
			if(store.lastOptions && store.lastOptions.params) {
				store.lastOptions.params[store.paramNames.start] = 0;
			}

			// get fields to search array
			var fields = [];
			this.menu.items.each(function(item) {
				if(item.checked) {
					fields.push(item.dataIndex);
				}
			});

			// add fields and query to baseParams of store
			delete(store.baseParams[this.paramNames.fields]);
			delete(store.baseParams[this.paramNames.query]);
			if (store.lastOptions && store.lastOptions.params) {
				delete(store.lastOptions.params[this.paramNames.fields]);
				delete(store.lastOptions.params[this.paramNames.query]);
			}
			if(fields.length) {
				store.baseParams[this.paramNames.fields] = Ext.encode(fields);
				store.baseParams[this.paramNames.query] = val;
			}

			// reload store
			store.reload();
		}

	} // eo function onTriggerSearch
	// }}}
	// {{{
	/**
	 * @param {Boolean} true to disable search (TwinTriggerField), false to enable
	 */
	,setDisabled:function() {
		this.field.setDisabled.apply(this.field, arguments);
	} // eo function setDisabled
	// }}}
	// {{{
	/**
	 * Enable search (TwinTriggerField)
	 */
	,enable:function() {
		this.setDisabled(false);
	} // eo function enable
	// }}}
	// {{{
	/**
	 * Disable search (TwinTriggerField)
	 */
	,disable:function() {
		this.setDisabled(true);
	} // eo function disable
	// }}}
	// {{{
	/**
	 * (re)configures the plugin, creates menu items from column model
	 * @private 
	 */
	,reconfigure:function() {

		// {{{
		// remove old items
		var menu = this.menu;
		menu.removeAll();

		// add Select All item plus separator
		if(this.showSelectAll && 'radio' !== this.menuStyle) {
			menu.add(new Ext.menu.CheckItem({
				 text:this.selectAllText
				,checked:!(this.checkIndexes instanceof Array)
				,hideOnClick:false
				,handler:function(item) {
					var checked = ! item.checked;
					item.parentMenu.items.each(function(i) {
						if(item !== i && i.setChecked && !i.disabled) {
							i.setChecked(checked);
						}
					});
				}
			}),'-');
		}

		// }}}
		// {{{
		// add new items
		var cm = this.grid.colModel;
		var group = undefined;
		if('radio' === this.menuStyle) {
			group = 'g' + (new Date).getTime();	
		}
		Ext.each(cm.config, function(config) {
			var disable = false;
			if(config.header && config.dataIndex) {
				Ext.each(this.disableIndexes, function(item) {
					disable = disable ? disable : item === config.dataIndex;
				});
				if(!disable) {
					menu.add(new Ext.menu.CheckItem({
						 text:config.header
						,hideOnClick:false
						,group:group
						,checked:'all' === this.checkIndexes
						,dataIndex:config.dataIndex
					}));
				}
			}
		}, this);
		// }}}
		// {{{
		// check items
		if(this.checkIndexes instanceof Array) {
			Ext.each(this.checkIndexes, function(di) {
				var item = menu.items.find(function(itm) {
					return itm.dataIndex === di;
				});
				if(item) {
					item.setChecked(true, true);
				}
			}, this);
		}
		// }}}
		// {{{
		// disable items
		if(this.readonlyIndexes instanceof Array) {
			Ext.each(this.readonlyIndexes, function(di) {
				var item = menu.items.find(function(itm) {
					return itm.dataIndex === di;
				});
				if(item) {
					item.disable();
				}
			}, this);
		}
		// }}}

	} // eo function reconfigure
	// }}}

}); // eo extend

// eof

/*!
 * Ext JS Library 3.1.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.namespace('Ext.ux.menu');

/** 
 * @class Ext.ux.menu.ListMenu
 * @extends Ext.menu.Menu
 * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.
 * Although not listed as configuration options for this class, this class
 * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.
 */
Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {
    /**
     * @cfg {String} labelField
     * Defaults to 'text'.
     */
    labelField :  'text',
    /**
     * @cfg {String} paramPrefix
     * Defaults to 'Loading...'.
     */
    loadingText : 'Loading...',
    /**
     * @cfg {Boolean} loadOnShow
     * Defaults to true.
     */
    loadOnShow : true,
    /**
     * @cfg {Boolean} single
     * Specify true to group all items in this list into a single-select
     * radio button group. Defaults to false.
     */
    single : false,

    constructor : function (cfg) {
        this.selected = [];
        this.addEvents(
            /**
             * @event checkchange
             * Fires when there is a change in checked items from this list
             * @param {Object} item Ext.menu.CheckItem
             * @param {Object} checked The checked value that was set
             */
            'checkchange'
        );
      
        Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {});
    
        if(!cfg.store && cfg.options){
            var options = [];
            for(var i=0, len=cfg.options.length; i<len; i++){
                var value = cfg.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]),
                data:   options,
                listeners: {
                    'load': this.onLoad,
                    scope:  this
                }
            });
            this.loaded = true;
        } else {
            this.add({text: this.loadingText, iconCls: 'loading-indicator'});
            this.store.on('load', this.onLoad, this);
        }
    },

    destroy : function () {
        if (this.store) {
            this.store.destroy();    
        }
        Ext.ux.menu.ListMenu.superclass.destroy.call(this);
    },

    /**
     * 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 adapter will
     * allow show to be called with no arguments to show with the previous arguments and
     * thus recalculate the width and potentially hang the menu from the left.
     */
    show : function () {
        var lastArgs = null;
        return function(){
            if(arguments.length === 0){
                Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs);
            } else {
                lastArgs = arguments;
                if (this.loadOnShow && !this.loaded) {
                    this.store.load();
                }
                Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments);
            }
        };
    }(),
    
    /** @private */
    onLoad : function (store, records) {
        var visible = this.isVisible();
        this.hide(false);
        
        this.removeAll(true);
        
        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.selected.indexOf(records[i].id) > -1,
                hideOnClick: false});
            
            item.itemId = records[i].id;
            item.on('checkchange', this.checkChange, this);
                        
            this.add(item);
        }
        
        this.loaded = true;
        
        if (visible) {
            this.show();
        }	
        this.fireEvent('load', this, records);
    },

    /**
     * Get the selected items.
     * @return {Array} selected
     */
    getSelected : function () {
        return this.selected;
    },
    
    /** @private */
    setSelected : function (value) {
        value = this.selected = [].concat(value);

        if (this.loaded) {
            this.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);
        }
    },
    
    /**
     * Handler for the 'checkchange' event from an check item in this menu
     * @param {Object} item Ext.menu.CheckItem
     * @param {Object} checked The checked value that was set
     */
    checkChange : function (item, checked) {
        var value = [];
        this.items.each(function(item){
            if (item.checked) {
                value.push(item.itemId);
            }
        },this);
        this.selected = value;
        
        this.fireEvent('checkchange', item, checked);
    }    
});
/*!
 * Ext JS Library 3.1.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.menu');

/** 
 * @class Ext.ux.menu.RangeMenu
 * @extends Ext.menu.Menu
 * Custom implementation of Ext.menu.Menu that has preconfigured
 * items for gt, lt, eq.
 * <p><b><u>Example Usage:</u></b></p>
 * <pre><code>    

 * </code></pre> 
 */
Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, {

    constructor : function (config) {

        Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config);

        this.addEvents(
            /**
             * @event update
             * Fires when a filter configuration has changed
             * @param {Ext.ux.grid.filter.Filter} this The filter object.
             */
            'update'
        );
      
        this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);
    
        var i, len, item, cfg, Cls;

        for (i = 0, len = this.menuItems.length; i < len; i++) {
            item = this.menuItems[i];
            if (item !== '-') {
                // defaults
                cfg = {
                    itemId: 'range-' + item,
                    enableKeyEvents: true,
                    iconCls: this.iconCls[item] || 'no-icon',
                    listeners: {
                        scope: this,
                        keyup: this.onInputKeyUp
                    }
                };
                Ext.apply(
                    cfg,
                    // custom configs
                    Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]),
                    // configurable defaults
                    this.menuItemCfgs
                );
                Cls = cfg.fieldCls || this.fieldCls;
                item = this.fields[item] = new Cls(cfg);
            }
            this.add(item);
        }
    },

    /**
     * @private
     * called by this.updateTask
     */
    fireUpdate : function () {
        this.fireEvent('update', this);
    },
    
    /**
     * Get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function () {
        var result = {}, key, field;
        for (key in this.fields) {
            field = this.fields[key];
            if (field.isValid() && String(field.getValue()).length > 0) {
                result[key] = field.getValue();
            }
        }
        return result;
    },
  
    /**
     * Set the value of this menu and fires the 'update' event.
     * @param {Object} data The data to assign to this menu
     */	
    setValue : function (data) {
        var key;
        for (key in this.fields) {
            this.fields[key].setValue(data[key] !== undefined ? data[key] : '');
        }
        this.fireEvent('update', this);
    },

    /**  
     * @private
     * Handler method called when there is a keyup event on an input
     * item of this menu.
     */
    onInputKeyUp : function (field, e) {
        var k = e.getKey();
        if (k == e.RETURN && field.isValid()) {
            e.stopEvent();
            this.hide(true);
            return;
        }
        
        if (field == this.fields.eq) {
            if (this.fields.gt) {
                this.fields.gt.setValue(null);
            }
            if (this.fields.lt) {
                this.fields.lt.setValue(null);
            }
        }
        else {
            this.fields.eq.setValue(null);
        }
        
        // restart the timer
        this.updateTask.delay(this.updateBuffer);
    }
});

/*!
 * Ext JS Library 3.1.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.namespace('Ext.ux.grid');

/**
 * @class Ext.ux.grid.GridFilters
 * @extends Ext.util.Observable
 * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that
 * allow for a slightly more robust representation of filtering than what is
 * provided by the default store.</p>
 * <p>Filtering is adjusted by the user using the grid's column header menu
 * (this menu can be disabled through configuration). Through this menu users
 * can configure, enable, and disable filters for each column.</p>
 * <p><b><u>Features:</u></b></p>
 * <div class="mdetail-params"><ul>
 * <li><b>Filtering implementations</b> :
 * <div class="sub-desc">
 * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can
 * be backed by a Ext.data.Store), and Boolean. Additional custom filter types
 * and menus are easily created by extending Ext.ux.grid.filter.Filter.
 * </div></li>
 * <li><b>Graphical indicators</b> :
 * <div class="sub-desc">
 * Columns that are filtered have {@link #filterCls a configurable css class}
 * applied to the column headers.
 * </div></li>
 * <li><b>Paging</b> :
 * <div class="sub-desc">
 * If specified as a plugin to the grid's configured PagingToolbar, the current page
 * will be reset to page 1 whenever you update the filters.
 * </div></li>
 * <li><b>Automatic Reconfiguration</b> :
 * <div class="sub-desc">
 * Filters automatically reconfigure when the grid 'reconfigure' event fires.
 * </div></li>
 * <li><b>Stateful</b> :
 * Filter information will be persisted across page loads by specifying a
 * <code>stateId</code> in the Grid configuration.
 * <div class="sub-desc">
 * The filter collection binds to the
 * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code>
 * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code>
 * events in order to be stateful. 
 * </div></li>
 * <li><b>Grid Changes</b> :
 * <div class="sub-desc"><ul>
 * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to
 * this plugin.</li>
 * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is
 * fired upon onStateChange completion.</li>
 * </ul></div></li>
 * <li><b>Server side code examples</b> :
 * <div class="sub-desc"><ul>
 * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li>
 * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li>
 * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li>
 * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li>
 * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li>
 * </ul></div></li>
 * </ul></div>
 * <p><b><u>Example usage:</u></b></p>
 * <pre><code>    
var store = new Ext.data.GroupingStore({
    ...
});
 
var filters = new Ext.ux.grid.GridFilters({
    autoReload: false, //don&#39;t reload automatically
    local: true, //only filter locally
    // filters may be configured through the plugin,
    // or in the column definition within the column model configuration
    filters: [{
        type: 'numeric',
        dataIndex: 'id'
    }, {
        type: 'string',
        dataIndex: 'name'
    }, {
        type: 'numeric',
        dataIndex: 'price'
    }, {
        type: 'date',
        dataIndex: 'dateAdded'
    }, {
        type: 'list',
        dataIndex: 'size',
        options: ['extra small', 'small', 'medium', 'large', 'extra large'],
        phpMode: true
    }, {
        type: 'boolean',
        dataIndex: 'visible'
    }]
});
var cm = new Ext.grid.ColumnModel([{
    ...
}]);
 
var grid = new Ext.grid.GridPanel({
     ds: store,
     cm: cm,
     view: new Ext.grid.GroupingView(),
     plugins: [filters],
     height: 400,
     width: 700,
     bbar: new Ext.PagingToolbar({
         store: store,
         pageSize: 15,
         plugins: [filters] //reset page to page 1 if filters change
     })
 });

store.load({params: {start: 0, limit: 15}});

// a filters property is added to the grid
grid.filters
 * </code></pre>
 */
Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {
    /**
     * @cfg {Boolean} autoReload
     * Defaults to true, reloading the datasource when a filter change happens.
     * Set this to false to prevent the datastore from being reloaded if there
     * are changes to the filters.  See <code>{@link updateBuffer}</code>.
     */
    autoReload : true,
    /**
     * @cfg {Boolean} encode
     * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to
     * encode the filter query parameter sent with a remote request.
     * Defaults to false.
     */
    /**
     * @cfg {Array} filters
     * An Array of filters config objects. Refer to each filter type class for
     * configuration details specific to each filter type. Filters for Strings,
     * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters
     * available.
     */
    /**
     * @cfg {String} filterCls
     * The css class to be applied to column headers with active filters.
     * Defaults to <tt>'ux-filterd-column'</tt>.
     */
    filterCls : 'ux-filtered-column',
    /**
     * @cfg {Boolean} local
     * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)
     * instead of the default (<tt>false</tt>) server side filtering.
     */
    local : false,
    /**
     * @cfg {String} menuFilterText
     * defaults to <tt>'Filters'</tt>.
     */
    menuFilterText : 'Filters',
    /**
     * @cfg {String} paramPrefix
     * The url parameter prefix for the filters.
     * Defaults to <tt>'filter'</tt>.
     */
    paramPrefix : 'filter',
    /**
     * @cfg {Boolean} showMenu
     * Defaults to true, including a filter submenu in the default header menu.
     */
    showMenu : true,
    /**
     * @cfg {String} stateId
     * Name of the value to be used to store state information.
     */
    stateId : undefined,
    /**
     * @cfg {Integer} updateBuffer
     * Number of milliseconds to defer store updates since the last filter change.
     */
    updateBuffer : 500,

    /** @private */
    constructor : function (config) {
        config = config || {};
        this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);
        this.filters = new Ext.util.MixedCollection();
        this.filters.getKey = function (o) {
            return o ? o.dataIndex : null;
        };
        this.addFilters(config.filters);
        delete config.filters;
        Ext.apply(this, config);
    },

    /** @private */
    init : function (grid) {
        if (grid instanceof Ext.grid.GridPanel) {
            this.grid = grid;
            
            this.bindStore(this.grid.getStore(), true);
            // assumes no filters were passed in the constructor, so try and use ones from the colModel
            if(this.filters.getCount() == 0){
                this.addFilters(this.grid.getColumnModel());
            }
          
            this.grid.filters = this;
             
            this.grid.addEvents({'filterupdate': true});
              
            grid.on({
                scope: this,
                beforestaterestore: this.applyState,
                beforestatesave: this.saveState,
                beforedestroy: this.destroy,
                reconfigure: this.onReconfigure
            });
            
            if (grid.rendered){
                this.onRender();
            } else {
                grid.on({
                    scope: this,
                    single: true,
                    render: this.onRender
                });
            }
                      
        } else if (grid instanceof Ext.PagingToolbar) {
            this.toolbar = grid;
        }
    },
        
    /**
     * @private
     * Handler for the grid's beforestaterestore event (fires before the state of the
     * grid is restored).
     * @param {Object} grid The grid object
     * @param {Object} state The hash of state values returned from the StateProvider.
     */   
    applyState : function (grid, state) {
        var key, filter;
        this.applyingState = true;
        this.clearFilters();
        if (state.filters) {
            for (key in state.filters) {
                filter = this.filters.get(key);
                if (filter) {
                    filter.setValue(state.filters[key]);
                    filter.setActive(true);
                }
            }
        }
        this.deferredUpdate.cancel();
        if (this.local) {
            this.reload();
        }
        delete this.applyingState;
    },
    
    /**
     * Saves the state of all active filters
     * @param {Object} grid
     * @param {Object} state
     * @return {Boolean}
     */
    saveState : function (grid, state) {
        var filters = {};
        this.filters.each(function (filter) {
            if (filter.active) {
                filters[filter.dataIndex] = filter.getValue();
            }
        });
        return (state.filters = filters);
    },
    
    /**
     * @private
     * Handler called when the grid is rendered
     */    
    onRender : function () {
        this.grid.getView().on('refresh', this.onRefresh, this);
        this.createMenu();
    },

    /**
     * @private
     * Handler called by the grid 'beforedestroy' event
     */    
    destroy : function () {
        this.removeAll();
        this.purgeListeners();

        if(this.filterMenu){
            Ext.menu.MenuMgr.unregister(this.filterMenu);
            this.filterMenu.destroy();
             this.filterMenu = this.menu.menu = null;            
        }
    },

    /**
     * Remove all filters, permanently destroying them.
     */    
    removeAll : function () {
        if(this.filters){
            Ext.destroy.apply(Ext, this.filters.items);
            // remove all items from the collection 
            this.filters.clear();
        }
    },


    /**
     * Changes the data store bound to this view and refreshes it.
     * @param {Store} store The store to bind to this view
     */
    bindStore : function(store, initial){
        if(!initial && this.store){
            if (this.local) {
                store.un('load', this.onLoad, this);
            } else {
                store.un('beforeload', this.onBeforeLoad, this);
            }
        }
        if(store){
            if (this.local) {
                store.on('load', this.onLoad, this);
            } else {
                store.on('beforeload', this.onBeforeLoad, this);
            }
        }
        this.store = store;
    },

    /**
     * @private
     * Handler called when the grid reconfigure event fires
     */    
    onReconfigure : function () {
        this.bindStore(this.grid.getStore());
        this.store.clearFilter();
        this.removeAll();
        this.addFilters(this.grid.getColumnModel());
        this.updateColumnHeadings();
    },

    createMenu : function () {
        var view = this.grid.getView(),
            hmenu = view.hmenu;

        if (this.showMenu && hmenu) {
            
            this.sep  = hmenu.addSeparator();
            this.filterMenu = new Ext.menu.Menu({
                id: this.grid.id + '-filters-menu'
            }); 
            this.menu = hmenu.add({
                checked: false,
                itemId: 'filters',
                text: this.menuFilterText,
                menu: this.filterMenu
            });

            this.menu.on({
                scope: this,
                checkchange: this.onCheckChange,
                beforecheckchange: this.onBeforeCheck
            });
            hmenu.on('beforeshow', this.onMenu, this);
        }
        this.updateColumnHeadings();
    },

    /**
     * @private
     * Get the filter menu from the filters MixedCollection based on the clicked header
     */
    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
     * Handler called by the grid's hmenu beforeshow event
     */    
    onMenu : function (filterMenu) {
        var filter = this.getMenuFilter();

        if (filter) {
/*            
TODO: lazy rendering
            if (!filter.menu) {
                filter.menu = filter.createMenu();
            }
*/
            this.menu.menu = filter.menu;
            this.menu.setChecked(filter.active, false);
            // disable the menu if filter.disabled explicitly set to true
            this.menu.setDisabled(filter.disabled === true);
        }
        
        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
     * Handler for all events on filters.
     * @param {String} event Event name
     * @param {Object} filter Standard signature of the event before the event is fired
     */   
    onStateChange : function (event, filter) {
        if (event === 'serialize') {
            return;
        }

        if (filter == this.getMenuFilter()) {
            this.menu.setChecked(filter.active, false);
        }

        if ((this.autoReload || this.local) && !this.applyingState) {
            this.deferredUpdate.delay(this.updateBuffer);
        }
        this.updateColumnHeadings();
            
        if (!this.applyingState) {
            this.grid.saveState();
        }    
        this.grid.fireEvent('filterupdate', this, filter);
    },
    
    /**
     * @private
     * Handler for store's beforeload event when configured for remote filtering
     * @param {Object} store
     * @param {Object} options
     */
    onBeforeLoad : function (store, options) {
        options.params = options.params || {};
        this.cleanParams(options.params);       
        var params = this.buildQuery(this.getFilterData());
        Ext.apply(options.params, params);
    },
    
    /**
     * @private
     * Handler for store's load event when configured for local filtering
     * @param {Object} store
     * @param {Object} options
     */
    onLoad : function (store, options) {
        store.filterBy(this.getRecordFilter());
    },

    /**
     * @private
     * Handler called when the grid's view is refreshed
     */    
    onRefresh : function () {
        this.updateColumnHeadings();
    },

    /**
     * Update the styles for the header row based on the active filters
     */    
    updateColumnHeadings : function () {
        var view = this.grid.getView(),
            hds, i, len, filter;
        if (view.mainHd) {
            hds = view.mainHd.select('td').removeClass(this.filterCls);
            for (i = 0, len = view.cm.config.length; i < len; i++) {
                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 {
            var start,
                store = this.grid.store;
            this.deferredUpdate.cancel();
            if (this.toolbar) {
                start = store.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 = [], len, i;
        this.filters.each(function (filter) {
            if (filter.active) {
                f.push(filter);
            }
        });
        
        len = f.length;
        return function (record) {
            for (i = 0; i < len; i++) {
                if (!f[i].validateRecord(record)) {
                    return false;
                }
            }
            return true;
        };
    },
    
    /**
     * Adds a filter to the collection and observes it for state change.
     * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.
     * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.
     */
    addFilter : function (config) {
        var Cls = this.getFilterClass(config.type),
            filter = config.menu ? config : (new Cls(config));
        this.filters.add(filter);
        
        Ext.util.Observable.capture(filter, this.onStateChange, this);
        return filter;
    },

    /**
     * Adds filters to the collection.
     * @param {Array/Ext.grid.ColumnModel} filters Either an Array of
     * filter configuration objects or an Ext.grid.ColumnModel.  The columns
     * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>
     * property and, if present, will be used as the filter configuration object.   
     */
    addFilters : function (filters) {
        if (filters) {
            var i, len, filter, cm = false, dI;
            if (filters instanceof Ext.grid.ColumnModel) {
                filters = filters.config;
                cm = true;
            }
            for (i = 0, len = filters.length; i < len; i++) {
                filter = false;
                if (cm) {
                    dI = filters[i].dataIndex;
                    filter = filters[i].filter || filters[i].filterable;
                    if (filter){
                        filter = (filter === true) ? {} : filter;
                        Ext.apply(filter, {dataIndex:dI});
                        // filter type is specified in order of preference:
                        //     filter type specified in config
                        //     type specified in store's field's type config
                        filter.type = filter.type || this.store.fields.get(dI).type;  
                    }
                } else {
                    filter = filters[i];
                }
                // if filter config found add filter for the column 
                if (filter) {
                    this.addFilter(filter);
                }
            }
        }
    },
    
    /**
     * Returns a filter for the given dataIndex, if one exists.
     * @param {String} dataIndex The dataIndex of the desired filter object.
     * @return {Ext.ux.grid.filter.Filter}
     */
    getFilter : function (dataIndex) {
        return this.filters.get(dataIndex);
    },

    /**
     * Turns all filters off. This does not clear the configuration information
     * (see {@link #removeAll}).
     */
    clearFilters : function () {
        this.filters.each(function (filter) {
            filter.setActive(false);
        });
    },

    /**
     * Returns an Array of the currently active filters.
     * @return {Array} filters Array of the currently active filters.
     */
    getFilterData : function () {
        var filters = [], i, len;

        this.filters.each(function (f) {
            if (f.active) {
                var d = [].concat(f.serialize());
                for (i = 0, len = d.length; i < len; i++) {
                    filters.push({
                        field: f.dataIndex,
                        data: d[i]
                    });
                }
            }
        });
        return filters;
    },
    
    /**
     * Function to take the active filters data and build it into a query.
     * The format of the query depends on the <code>{@link #encode}</code>
     * configuration:
     * <div class="mdetail-params"><ul>
     * 
     * <li><b><tt>false</tt></b> : <i>Default</i>
     * <div class="sub-desc">
     * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:
     * <pre><code>
filters[0][field]="someDataIndex"&
filters[0][data][comparison]="someValue1"&
filters[0][data][type]="someValue2"&
filters[0][data][value]="someValue3"&
     * </code></pre>
     * </div></li>
     * <li><b><tt>true</tt></b> : 
     * <div class="sub-desc">
     * JSON encode the filter data
     * <pre><code>
filters[0][field]="someDataIndex"&
filters[0][data][comparison]="someValue1"&
filters[0][data][type]="someValue2"&
filters[0][data][value]="someValue3"&
     * </code></pre>
     * </div></li>
     * </ul></div>
     * Override this method to customize the format of the filter query for remote requests.
     * @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 = {}, i, f, root, dataPrefix, key, tmp,
            len = filters.length;

        if (!this.encode){
            for (i = 0; i < len; i++) {
                f = filters[i];
                root = [this.paramPrefix, '[', i, ']'].join('');
                p[root + '[field]'] = f.field;
                
                dataPrefix = root + '[data]';
                for (key in f.data) {
                    p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];
                }
            }
        } else {
            tmp = [];
            for (i = 0; i < len; i++) {
                f = filters[i];
                tmp.push(Ext.apply(
                    {},
                    {field: f.field},
                    f.data
                ));
            }
            // only build if there is active filter 
            if (tmp.length > 0){
                p[this.paramPrefix] = Ext.util.JSON.encode(tmp);
            }
        }
        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) {
        // if encoding just delete the property
        if (this.encode) {
            delete p[this.paramPrefix];
        // otherwise scrub the object of filter data
        } else {
            var regex, key;
            regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');
            for (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 ('Filter' is automatically
     * appended to the passed type; eg, 'string' becomes 'StringFilter').
     * @return {Class} The Ext.ux.grid.filter.Class 
     */
    getFilterClass : function (type) {
        // map the supported Ext.data.Field type values into a supported filter
        switch(type) {
            case 'auto':
              type = 'string';
              break;
            case 'int':
            case 'float':
              type = 'numeric';
              break;
        }
        return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];
    }
});

// register ptype
Ext.preg('gridfilters', Ext.ux.grid.GridFilters);

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.MidasCommand
 * @extends Ext.util.Observable
 * <p>A base plugin for extending to create standard Midas command buttons.</p>
 * http://msdn.microsoft.com/en-us/library/ms533049%28v=VS.85%29.aspx
 * http://www.mozilla.org/editor/midas-spec.html
 */
Ext.ns('Ext.ux.form.HtmlEditor');

if (!Ext.isObject) {
    Ext.isObject = function(v){
        return v && typeof v == "object";
    };
}

Ext.override(Ext.form.HtmlEditor, {
    getSelectedText: function(clip){
        var doc = this.getDoc(), selDocFrag;
        var txt = '', hasHTML = false, selNodes = [], ret, html = '';
        if (this.win.getSelection || doc.getSelection) {
            // FF, Chrome, Safari
            var sel = this.win.getSelection();
            if (!sel) {
                sel = doc.getSelection();
            }
            if (clip) {
                selDocFrag = sel.getRangeAt(0).extractContents();
            } else {
                selDocFrag = this.win.getSelection().getRangeAt(0).cloneContents();
            }
            Ext.each(selDocFrag.childNodes, function(n){
                if (n.nodeType !== 3) {
                    hasHTML = true;
                }
            });
            if (hasHTML) {
                var div = document.createElement('div');
                div.appendChild(selDocFrag);
                html = div.innerHTML;
                txt = this.win.getSelection() + '';
            } else {
                html = txt = selDocFrag.textContent;
            }
            ret = {
                textContent: txt,
                hasHTML: hasHTML,
                html: html
            };
        } else if (doc.selection) {
            // IE
            this.win.focus();
            txt = doc.selection.createRange();
            if (txt.text !== txt.htmlText) {
                hasHTML = true;
            }
            ret = {
                textContent: txt.text,
                hasHTML: hasHTML,
                html: txt.htmlText
            };
        } else {
            return {
                textContent: ''
            };
        }
        
        return ret;
    }
});

Ext.ux.form.HtmlEditor.MidasCommand = Ext.extend(Ext.util.Observable, {
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.btns = [];
        this.cmp.on('render', this.onRender, this);
        this.cmp.on('initialize', this.onInit, this, {
            delay: 100,
            single: true
        });
    },
    // private
    onInit: function(){
        Ext.EventManager.on(this.cmp.getDoc(), {
            'mousedown': this.onEditorEvent,
            'dblclick': this.onEditorEvent,
            'click': this.onEditorEvent,
            'keyup': this.onEditorEvent,
            buffer: 100,
            scope: this
        });
    },
    // private
    onRender: function(){
        var midasCmdButton, tb = this.cmp.getToolbar(), btn, iconCls;
        Ext.each(this.midasBtns, function(b){
            if (Ext.isObject(b)) {
                iconCls = (b.iconCls) ? b.iconCls : 'x-edit-' + b.cmd;
                if (b.value) { iconCls = iconCls+'-'+b.value.replace(/[<>\/]/g,''); }
                midasCmdButton = {
                    iconCls: iconCls,
                    handler: function(){
                        this.cmp.relayCmd(b.cmd, b.value);
                    },
                    scope: this,
                    tooltip: b.tooltip ||
                    {
                        title: b.title
                    },
                    overflowText: b.overflowText || b.title
                };
            } else {
                midasCmdButton = new Ext.Toolbar.Separator();
            }
            btn = tb.addButton(midasCmdButton);
            if (b.enableOnSelection) {
                btn.disable();
            }
            this.btns.push(btn);
        }, this);
    },
    // private
    onEditorEvent: function(){
        var doc = this.cmp.getDoc();
        Ext.each(this.btns, function(b, i){
            if (this.midasBtns[i].enableOnSelection || this.midasBtns[i].disableOnSelection) {
                if (doc.getSelection) {
                    if ((this.midasBtns[i].enableOnSelection && doc.getSelection() !== '') || (this.midasBtns[i].disableOnSelection && doc.getSelection() === '')) {
                        b.enable();
                    } else {
                        b.disable();
                    }
                } else if (doc.selection) {
                    if ((this.midasBtns[i].enableOnSelection && doc.selection.createRange().text !== '') || (this.midasBtns[i].disableOnSelection && doc.selection.createRange().text === '')) {
                        b.enable();
                    } else {
                        b.disable();
                    }
                }
            }
            if (this.midasBtns[i].monitorCmdState) {
                b.toggle(doc.queryCommandState(this.midasBtns[i].cmd));
            }
        }, this);
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.Divider
 * @extends Ext.util.Observable
 * <p>A plugin that creates a divider on the HtmlEditor. Used for separating additional buttons.</p>
 */
Ext.ux.form.HtmlEditor.Divider = Ext.extend(Ext.util.Observable, {
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        this.cmp.getToolbar().addButton([new Ext.Toolbar.Separator()]);
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @contributor Ronald van Raaphorst - Twensoc
 * @class Ext.ux.form.HtmlEditor.FindReplace
 * @extends Ext.util.Observable
 * <p>A plugin that provides search and replace functionality in source edit mode. Incomplete.</p>
 */
Ext.ux.form.HtmlEditor.FindAndReplace = Ext.extend(Ext.util.Observable, {
	// Find and Replace language text
	langTitle: 'Find/Replace',
	langFind: 'Find',
	langReplace: 'Replace',
	langReplaceWith: 'Replace with',
	langClose: 'Close',
    // private
    cmd: 'findandreplace',
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on({
            'render': this.onRender,
            'editmodechange': this.editModeChange,
            scope: this
        });
        this.lastSelectionStart=-1;
    },
    editModeChange: function(t, m){
        if (this.btn && m){
            this.btn.setDisabled(false);
        }
    },
    // private
    onRender: function(){
        this.btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-findandreplace',
            sourceEditEnabled:true,
            handler: function(){
                
                if (!this.farWindow){
                
                    this.farWindow = new Ext.Window({
                        title: this.langTitle,
                        closeAction: 'hide',
                        width: 270,
                        items: [{
                            itemId: 'findandreplace',
                            xtype: 'form',
                            border: false,
                            plain: true,
                            bodyStyle: 'padding: 10px;',
                            labelWidth: 80,
                            labelAlign: 'right',
                            items: [{
                                xtype: 'textfield',
                                allowBlank: false,
                                fieldLabel: this.langFind,
                                name: 'find',
                                width: 145
                            }, {
                                xtype: 'textfield',
                                allowBlank: true,
                                fieldLabel: this.langReplaceWith,
                                name: 'replace',
                                width: 145
                            }]
                        }],
                        buttons: [{
                            text: this.langFind,
                            handler: this.doFind,
                            scope: this
                        }, {
                            text: this.langReplace,
                            handler: this.doReplace,
                            scope: this
                        }, {
                            text: this.langClose,
                            handler: function(){
                                this.farWindow.hide();
                            },
                            scope: this
                        }]
                    });
                
                }else{
                    
                    this.farWindow.getEl().frame();
                    
                }
                
                this.farWindow.show();
                
            },
            scope: this,
            tooltip: {
                title: this.langTitle
            },
            overflowText: this.langTitle
        });
        
    },
    doFind: function(){
        
        var frm = this.farWindow.getComponent('findandreplace').getForm();
        if (!frm.isValid()) {
            return '';
        }
        
        var findValue = frm.findField('find').getValue();
        var replaceValue = frm.findField('replace').getValue();
        if(this.cmp.sourceEditMode) {
            // source edit mode
            var textarea = this.cmp.el.dom; 
            var startPos = textarea.selectionStart===this.lastSelectionStart ? textarea.selectionStart+1 : textarea.selectionStart;
            var txt = textarea.value.substring(startPos);
            
            var regexp = new RegExp(findValue);
            var r = txt.search(regexp);
            if(r==-1) {
                return;                                    
            }
            this.lastSelectionStart = startPos + r;
            if(Ext.isGecko) {
                textarea.setSelectionRange(this.lastSelectionStart , this.lastSelectionStart + findValue.length);
                this.cmp.scrollIntoView(startPos);
                this.cmp.focus(false, true);
            }
            return;
        }
        // design mode
        //var doc = this.cmp.getEditorBody();
        //var txt = doc.innerHTML;
        // Should we search/replace in the source, and push the result back to the design?
        
    },
    doReplace: function(){
        
        var frm = this.farWindow.getComponent('findandreplace').getForm();
        if (!frm.isValid()) {
            return '';
        }
        
        var findValue = frm.findField('find').getValue();
        var replaceValue = frm.findField('replace').getValue();
        if(this.cmp.sourceEditMode) {
            var textarea = this.cmp.el.dom;
            var startPos = textarea.selectionStart;
            var endPos = textarea.selectionEnd;
            var txt = textarea.value;
            
            //cmp.execCmd('delete', null);
            //cmp.focus(false, false);
            //cmp.insertAtCursor(replaceValue);

            if(Ext.isGecko) {
                // TODO: Scroll into view
                var scrollPosition = textarea.scrollTop;
                textarea.value = txt.substring(0,startPos) + replaceValue + txt.substring(endPos);
                textarea.setSelectionRange(startPos,startPos + replaceValue.length);
                textarea.scrollTop = scrollPosition;
                this.cmp.focus(false, true);
            }
            return;
        }
        return;
        
    }
});
/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.Font
 * @extends Ext.util.Observable
 * <p>A plugin that creates a menu on the HtmlEditor for selecting a font. Uses the HtmlEditors default font settings which can be overriden on that component to change the list of fonts or default font.</p>
 */
Ext.ux.form.HtmlEditor.Font = Ext.extend(Ext.util.Observable, {
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var fonts = function(){
            var fnts = [];
            Ext.each(cmp.fontFamilies, function(itm){
                fnts.push([itm.toLowerCase(),itm]);
            });
            return fnts;
        }(); 
        var btn = this.cmp.getToolbar().addItem({
            xtype: 'combo',
            displayField: 'display',
            valueField: 'value',
            name: 'fontfamily',
            forceSelection: true,
            mode: 'local',
            triggerAction: 'all',
            width: 80,
            emptyText: 'Font',
            tpl: '<tpl for="."><div class="x-combo-list-item" style="font-family:{value};">{display}</div></tpl>',
            store: {
                xtype: 'arraystore',
                autoDestroy: true,
                fields: ['value','display'],
                data: fonts
            },
            listeners: {
                'select': function(combo,rec){
                    this.relayCmd('fontname', rec.get('value'));
                    this.deferFocus();
                    combo.reset();
                },
                scope: cmp
            }
        });
    }
});
/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @contributor Somani - http://www.sencha.com/forum/member.php?51567-Somani
 * @class Ext.ux.form.HtmlEditor.HeadingButtons
 * @extends Ext.ux.form.HtmlEditor.MidasCommand
 * <p>A plugin that creates a button on the HtmlEditor that will have H1 and H2 options. This is used when you want to restrict users to just a few heading types.</p>
 * NOTE: while 'heading' should be the command used, it is not supported in IE, so 'formatblock' is used instead. Thank you IE.
 */

Ext.ux.form.HtmlEditor.HeadingButtons = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    // private
    midasBtns: ['|', {
        enableOnSelection: true,
        cmd: 'formatblock',
        value: '<h1>',
        tooltip: {
            title: '1st Heading'
        },
        overflowText: '1st Heading'
    }, {
        enableOnSelection: true,
        cmd: 'formatblock',
        value: '<h2>',
        tooltip: {
            title: '2nd Heading'
        },
        overflowText: '2nd Heading'
    }]
}); 

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.HeadingMenu
 * @extends Ext.util.Observable
 * <p>A plugin that creates a menu on the HtmlEditor for selecting a heading size. Takes up less room than the heading buttons if your going to have all six heading sizes available.</p>
 */
Ext.ux.form.HtmlEditor.HeadingMenu = Ext.extend(Ext.util.Observable, {
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addItem({
            xtype: 'combo',
            displayField: 'display',
            valueField: 'value',
            name: 'headingsize',
            forceSelection: true,
            mode: 'local',
            triggerAction: 'all',
            width: 65,
            emptyText: 'Heading',
            store: {
                xtype: 'arraystore',
                autoDestroy: true,
                fields: ['value','display'],
                data: [['H1','1st Heading'],['H2','2nd Heading'],['H3','3rd Heading'],['H4','4th Heading'],['H5','5th Heading'],['H6','6th Heading']]
            },
            listeners: {
                'select': function(combo,rec){
                    this.relayCmd('formatblock', '<'+rec.get('value')+'>');
                    combo.reset();
                },
                scope: cmp
            }
        });
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.HR
 * @extends Ext.util.Observable
 * <p>A plugin that creates a button on the HtmlEditor for inserting a horizontal rule.</p>
 */
Ext.ux.form.HtmlEditor.HR = Ext.extend(Ext.util.Observable, {
    // HR language text
    langTitle   : 'Horizontal Rule',
    langHelp    : 'Enter the width of the Rule in percentage<br/> followed by the % sign at the end, or to<br/> set a fixed width ommit the % symbol.',
    langInsert  : 'Insert',
    langCancel  : 'Cancel',
    langWidth   : 'Width',
    // defaults
    defaultHRWidth: '100%',
    // private
    cmd: 'hr',
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-hr',
            handler: function(){
                if (!this.hrWindow) {
                    this.hrWindow = new Ext.Window({
                        title: this.langTitle,
                        width: 240,
                        closeAction: 'hide',
                        items: [{
                            itemId: 'insert-hr',
                            xtype: 'form',
                            border: false,
                            plain: true,
                            bodyStyle: 'padding: 10px;',
                            labelWidth: 60,
                            labelAlign: 'right',
                            items: [{
                                xtype: 'label',
                                html: this.langHelp + '<br/>&nbsp;'
                            }, {
                                xtype: 'textfield',
                                maskRe: /[0-9]|%/,
                                regex: /^[1-9][0-9%]{1,3}/,
                                fieldLabel: this.langWidth,
                                name: 'hrwidth',
                                width: 60,
                                value: this.defaultHRWidth,
                                listeners: {
                                    specialkey: function(f, e){
                                        if ((e.getKey() == e.ENTER || e.getKey() == e.RETURN) && f.isValid()) {
                                            this.doInsertHR();
                                        }
                                    },
                                    scope: this
                                }
                            }]
                        }],
                        buttons: [{
                            text: this.langInsert,
                            handler: function(){
                                var frm = this.hrWindow.getComponent('insert-hr').getForm();
                                if (frm.isValid()) {
                                    this.doInsertHR();
                                } else {
                                    frm.findField('hrwidth').getEl().frame();
                                }
                            },
                            scope: this
                        }, {
                            text: this.langCancel,
                            handler: function(){
                                this.hrWindow.hide();
                            },
                            scope: this
                        }],
                        listeners: {
                            render: (Ext.isGecko) ? this.focusHRLong : this.focusHR,
                            show: this.focusHR,
                            move: this.focusHR,
                            scope: this
                        }
                    });
                } else {
                    this.hrWindow.getEl().frame();
                }
                this.hrWindow.show();
            },
            scope: this,
            tooltip: {
                title: this.langInsert + ' ' + this.langTitle
            },
            overflowText: this.langTitle
        });
    },
    // private
    focusHRLong: function(w){
        this.focus(w, 600);
    },
    // private
    focusHR: function(w){
        this.focus(w, 100);
    },
    /**
     * This method is just for focusing the text field use for entering the width of the HR.
     * It's extra messy because Firefox seems to take a while longer to render the window than other browsers, 
     * particularly when Firbug is enabled, which is all the time if your like me.
     * Had to crank up the delay for focusing on render to 600ms for Firefox, and 100ms for all other focusing.
     * Other browsers seem to work fine in all cases with as little as 50ms delay. Compromise bleh!
     * @param {Object} win the window to focus
     * @param {Integer} delay the delay in milliseconds before focusing
     */
    focus: function(win, delay){
        win.getComponent('insert-hr').getForm().findField('hrwidth').focus(true, delay);
    },
    // private
    doInsertHR: function(){
        var frm = this.hrWindow.getComponent('insert-hr').getForm();
        if (frm.isValid()) {
            var hrwidth = frm.findField('hrwidth').getValue();
            if (hrwidth) {
                this.insertHR(hrwidth);
            } else {
                this.insertHR(this.defaultHRWidth);
            }
            frm.reset();
            this.hrWindow.hide();
        }
    },
    /**
     * Insert a horizontal rule into the document.
     * @param w String The width of the horizontal rule as the <tt>width</tt> attribute of the HR tag expects. ie: '100%' or '400' (pixels).
     */
    insertHR: function(w){
        this.cmp.insertAtCursor('<hr width="' + w + '">');
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.Image
 * @extends Ext.util.Observable
 * <p>A plugin that creates an image button in the HtmlEditor toolbar for inserting an image. The method to select an image must be defined by overriding the selectImage method. Supports resizing of the image after insertion.</p>
 * <p>The selectImage implementation must call insertImage after the user has selected an image, passing it a simple image object like the one below.</p>
 * <pre>
 *      var img = {
 *         Width: 100,
 *         Height: 100,
 *         ID: 123,
 *         Title: 'My Image'
 *      };
 * </pre>
 */
Ext.ux.form.HtmlEditor.Image = Ext.extend(Ext.util.Observable, {
	// Image language text
	langTitle: 'Insert Image',
    urlSizeVars: ['width','height'],
    basePath: 'image.php',
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
        this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
    },
    onEditorMouseUp : function(e){
        Ext.get(e.getTarget()).select('img').each(function(el){
            var w = el.getAttribute('width'), h = el.getAttribute('height'), src = el.getAttribute('src')+' ';
            src = src.replace(new RegExp(this.urlSizeVars[0]+'=[0-9]{1,5}([&| ])'), this.urlSizeVars[0]+'='+w+'$1');
            src = src.replace(new RegExp(this.urlSizeVars[1]+'=[0-9]{1,5}([&| ])'), this.urlSizeVars[1]+'='+h+'$1');
            el.set({src:src.replace(/\s+$/,"")});
        }, this);
        
    },
    onInit: function(){
        Ext.EventManager.on(this.cmp.getDoc(), {
			'mouseup': this.onEditorMouseUp,
			buffer: 100,
			scope: this
		});
    },
    onRender: function() {
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-image',
            handler: this.selectImage,
            scope: this,
            tooltip: {
                title: this.langTitle
            },
            overflowText: this.langTitle
        });
    },
    selectImage: Ext.emptyFn,
    insertImage: function(img) {
        this.cmp.insertAtCursor('<img src="'+this.basePath+'?'+this.urlSizeVars[0]+'='+img.Width+'&'+this.urlSizeVars[1]+'='+img.Height+'&id='+img.ID+'" title="'+img.Name+'" alt="'+img.Name+'">');
    }
});
/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.IndentOutdent
 * @extends Ext.ux.form.HtmlEditor.MidasCommand
 * <p>A plugin that creates two buttons on the HtmlEditor for indenting and outdenting of selected text.</p>
 */
Ext.ux.form.HtmlEditor.IndentOutdent = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    // private
    midasBtns: ['|', {
        cmd: 'indent',
        tooltip: {
            title: 'Indent Text'
        },
        overflowText: 'Indent Text'
    }, {
        cmd: 'outdent',
        tooltip: {
            title: 'Outdent Text'
        },
        overflowText: 'Outdent Text'
    }]
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.Link
 * @extends Ext.util.Observable
 * <p>A plugin that creates a button on the HtmlEditor for inserting a link.</p>
 */
Ext.ux.form.HtmlEditor.Link = Ext.extend(Ext.util.Observable, {
    // Link language text
    langTitle   : 'Insert Link',
    langInsert  : 'Insert',
    langCancel  : 'Cancel',
    langTarget  : 'Target',
    langURL     : 'URL',
    langText    : 'Text',
    // private
    linkTargetOptions: [['_self', 'Default'], ['_blank', 'New Window'], ['_parent', 'Parent Window'], ['_top', 'Entire Window']],
    init: function(cmp){
        cmp.enableLinks = false;
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-createlink',
            handler: function(){
                var sel = this.cmp.getSelectedText();
                if (!this.linkWindow) {
                    this.linkWindow = new Ext.Window({
                        title: this.langTitle,
                        closeAction: 'hide',
                        width: 250,
                        height: 160,
                        items: [{
                            xtype: 'form',
                            itemId: 'insert-link',
                            border: false,
                            plain: true,
                            bodyStyle: 'padding: 10px;',
                            labelWidth: 40,
                            labelAlign: 'right',
                            items: [{
                                xtype: 'textfield',
                                fieldLabel: this.langText,
                                name: 'text',
                                anchor: '100%',
                                value: '' 
                            }, {
                                xtype: 'textfield',
                                fieldLabel: this.langURL,
                                vtype: 'url',
                                name: 'url',
                                anchor: '100%',
                                value: 'http://'
                            }, {
                                xtype: 'combo',
                                fieldLabel: this.langTarget,
                                name: 'target',
                                forceSelection: true,
                                mode: 'local',
                                store: new Ext.data.ArrayStore({
                                    autoDestroy: true,
                                    fields: ['spec', 'val'],
                                    data: this.linkTargetOptions
                                }),
                                triggerAction: 'all',
                                value: '_self',
                                displayField: 'val',
                                valueField: 'spec',
                                anchor: '100%'
                            }]
                        }],
                        buttons: [{
                            text: this.langInsert,
                            handler: function(){
                                var frm = this.linkWindow.getComponent('insert-link').getForm();
                                if (frm.isValid()) {
                                    var afterSpace = '', sel = this.cmp.getSelectedText(true), text = frm.findField('text').getValue(), url = frm.findField('url').getValue(), target = frm.findField('target').getValue();
                                    if (text.length && text[text.length - 1] == ' ') {
                                        text = text.substr(0, text.length - 1);
                                        afterSpace = ' ';
                                    }
                                    if (sel.hasHTML) {
                                        text = sel.html;
                                    }
                                    var html = '<a href="' + url + '" target="' + target + '">' + text + '</a>' + afterSpace;
                                    this.cmp.insertAtCursor(html);
                                    this.linkWindow.hide();
                                } else {
                                    if (!frm.findField('url').isValid()) {
                                        frm.findField('url').getEl().frame();
                                    } else if (!frm.findField('target').isValid()) {
                                        frm.findField('target').getEl().frame();
                                    }
                                }
                                
                            },
                            scope: this
                        }, {
                            text: this.langCancel,
                            handler: function(){
                                this.linkWindow.close();
                            },
                            scope: this
                        }],
                        listeners: {
                            show: {
                                fn: function(){
                                    var frm = this.linkWindow.getComponent('insert-link').getForm();
                                    frm.findField('text').setValue(sel.textContent).setDisabled(sel.hasHTML);
                                    frm.findField('url').reset().focus(true, 50);
                                },
                                scope: this,
                                defer: 350
                            }
                        }
                    });
                    this.linkWindow.show();
                } else {
                    this.linkWindow.show();
                    this.linkWindow.getEl().frame();
                }
            },
            scope: this,
            tooltip: this.langTitle
        });
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.plugins
 * <p>A convenience function that returns a standard set of HtmlEditor buttons.</p>
 * <p>Sample usage:</p>
 * <pre><code>
    new Ext.FormPanel({
        ...
        items : [{
            ...
            xtype           : "htmleditor",
            plugins         : Ext.ux.form.HtmlEditor.plugins()
        }]
    });
 * </code></pre>
 */
Ext.ux.form.HtmlEditor.plugins = function(){
    return [
        new Ext.ux.form.HtmlEditor.Link(),
        new Ext.ux.form.HtmlEditor.Divider(),
        new Ext.ux.form.HtmlEditor.Word(),
        new Ext.ux.form.HtmlEditor.FindAndReplace(),
        new Ext.ux.form.HtmlEditor.UndoRedo(),
        new Ext.ux.form.HtmlEditor.Divider(),
        new Ext.ux.form.HtmlEditor.Image(),
        new Ext.ux.form.HtmlEditor.Table(),
        new Ext.ux.form.HtmlEditor.HR(),
        new Ext.ux.form.HtmlEditor.SpecialCharacters(),
        new Ext.ux.form.HtmlEditor.HeadingMenu(),
        new Ext.ux.form.HtmlEditor.IndentOutdent(),
        new Ext.ux.form.HtmlEditor.SubSuperScript(),
        new Ext.ux.form.HtmlEditor.RemoveFormat()
    ];
};
/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.RemoveFormat
 * @extends Ext.ux.form.HtmlEditor.MidasCommand
 * <p>A plugin that creates a button on the HtmlEditor that will remove all formatting on selected text.</p>
 */
Ext.ux.form.HtmlEditor.RemoveFormat = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    midasBtns: ['|', {
        enableOnSelection: true,
        cmd: 'removeFormat',
        tooltip: {
            title: 'Remove Formatting'
        },
        overflowText: 'Remove Formatting'
    }]
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.SpecialCharacters
 * @extends Ext.util.Observable
 * <p>A plugin that creates a button on the HtmlEditor for inserting special characters.</p>
 */
Ext.ux.form.HtmlEditor.SpecialCharacters = Ext.extend(Ext.util.Observable, {
    // SpecialCharacters language text
    langTitle   : 'Insert Special Character',
    langInsert  : 'Insert',
    langCancel  : 'Cancel',
    /**
     * @cfg {Array} specialChars
     * An array of additional characters to display for user selection.  Uses numeric portion of the ASCII HTML Character Code only. For example, to use the Copyright symbol, which is &#169; we would just specify <tt>169</tt> (ie: <tt>specialChars:[169]</tt>).
     */
    specialChars: [153],
    /**
     * @cfg {Array} charRange
     * Two numbers specifying a range of ASCII HTML Characters to display for user selection. Defaults to <tt>[160, 256]</tt>.
     */
    charRange: [160, 256],
    // private
    chars: [],
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-char',
            handler: function(){
                if (!this.chars.length) {
                    if (this.specialChars.length) {
                        Ext.each(this.specialChars, function(c, i){
                            this.chars[i] = ['&#' + c + ';'];
                        }, this);
                    }
                    for (i = this.charRange[0]; i < this.charRange[1]; i++) {
                        this.chars.push(['&#' + i + ';']);
                    }
                }
                var charStore = new Ext.data.ArrayStore({
                    fields: ['char'],
                    data: this.chars
                });
                this.charWindow = new Ext.Window({
                    title: this.langTitle,
                    width: 436,
                    autoHeight: true,
                    layout: 'fit',
                    items: [{
                        xtype: 'dataview',
                        store: charStore,
                        ref: 'charView',
                        autoHeight: true,
                        multiSelect: true,
                        tpl: new Ext.XTemplate('<tpl for="."><div class="char-item">{char}</div></tpl><div class="x-clear"></div>'),
                        overClass: 'char-over',
                        itemSelector: 'div.char-item',
                        listeners: {
                            dblclick: function(t, i, n, e){
                                this.insertChar(t.getStore().getAt(i).get('char'));
                                this.charWindow.close();
                            },
                            scope: this
                        }
                    }],
                    buttons: [{
                        text: this.langInsert,
                        handler: function(){
                            Ext.each(this.charWindow.charView.getSelectedRecords(), function(rec){
                                var c = rec.get('char');
                                this.insertChar(c);
                            }, this);
                            this.charWindow.close();
                        },
                        scope: this
                    }, {
                        text: this.langCancel,
                        handler: function(){
                            this.charWindow.close();
                        },
                        scope: this
                    }]
                });
                this.charWindow.show();
            },
            scope: this,
            tooltip: {
                title: this.langTitle
            },
            overflowText: this.langTitle
        });
    },
    /**
     * Insert a single special character into the document.
     * @param c String The special character to insert (not just the numeric code, but the entire ASCII HTML entity).
     */
    insertChar: function(c){
        if (c) {
            this.cmp.insertAtCursor(c);
        }
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.SubSuperScript
 * @extends Ext.ux.form.HtmlEditor.MidasCommand
 * <p>A plugin that creates two buttons on the HtmlEditor for superscript and subscripting of selected text.</p>
 */
Ext.ux.form.HtmlEditor.SubSuperScript = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    // private
    midasBtns: ['|', {
        enableOnSelection: true,
        cmd: 'subscript',
        tooltip: {
            title: 'Subscript'
        },
        overflowText: 'Subscript'
    }, {
        enableOnSelection: true,
        cmd: 'superscript',
        tooltip: {
            title: 'Superscript'
        },
        overflowText: 'Superscript'
    }]
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.Table
 * @extends Ext.util.Observable
 * <p>A plugin that creates a button on the HtmlEditor for making simple tables.</p>
 */
Ext.ux.form.HtmlEditor.Table = Ext.extend(Ext.util.Observable, {
    // Table language text
    langTitle       : 'Insert Table',
    langInsert      : 'Insert',
    langCancel      : 'Cancel',
    langRows        : 'Rows',
    langColumns     : 'Columns',
    langBorder      : 'Border',
    langCellLabel   : 'Label Cells',
    // private
    cmd: 'table',
    /**
     * @cfg {Boolean} showCellLocationText
     * Set true to display row and column informational text inside of newly created table cells.
     */
    showCellLocationText: true,
    /**
     * @cfg {String} cellLocationText
     * The string to display inside of newly created table cells.
     */
    cellLocationText: '{0}&nbsp;-&nbsp;{1}',
    /**
     * @cfg {Array} tableBorderOptions
     * A nested array of value/display options to present to the user for table border style. Defaults to a simple list of 5 varrying border types.
     */
    tableBorderOptions: [['none', 'None'], ['1px solid #000', 'Sold Thin'], ['2px solid #000', 'Solid Thick'], ['1px dashed #000', 'Dashed'], ['1px dotted #000', 'Dotted']],
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-table',
            handler: function(){
                if (!this.tableWindow){
                    this.tableWindow = new Ext.Window({
                        title: this.langTitle,
                        closeAction: 'hide',
                        width: 235,
                        items: [{
                            itemId: 'insert-table',
                            xtype: 'form',
                            border: false,
                            plain: true,
                            bodyStyle: 'padding: 10px;',
                            labelWidth: 65,
                            labelAlign: 'right',
                            items: [{
                                xtype: 'numberfield',
                                allowBlank: false,
                                allowDecimals: false,
                                fieldLabel: this.langRows,
                                name: 'row',
                                width: 60
                            }, {
                                xtype: 'numberfield',
                                allowBlank: false,
                                allowDecimals: false,
                                fieldLabel: this.langColumns,
                                name: 'col',
                                width: 60
                            }, {
                                xtype: 'combo',
                                fieldLabel: this.langBorder,
                                name: 'border',
                                forceSelection: true,
                                mode: 'local',
                                store: new Ext.data.ArrayStore({
                                    autoDestroy: true,
                                    fields: ['spec', 'val'],
                                    data: this.tableBorderOptions
                                }),
                                triggerAction: 'all',
                                value: 'none',
                                displayField: 'val',
                                valueField: 'spec',
                                anchor: '-15'
                            }, {
                            	xtype: 'checkbox',
                            	fieldLabel: this.langCellLabel,
                            	checked: this.showCellLocationText,
                            	listeners: {
                            		check: function(){
                            			this.showCellLocationText = !this.showCellLocationText;
                            		},
                            		scope: this
                            	}
                            }]
                        }],
                        buttons: [{
                            text: this.langInsert,
                            handler: function(){
                                var frm = this.tableWindow.getComponent('insert-table').getForm();
                                if (frm.isValid()) {
                                    var border = frm.findField('border').getValue();
                                    var rowcol = [frm.findField('row').getValue(), frm.findField('col').getValue()];
                                    if (rowcol.length == 2 && rowcol[0] > 0 && rowcol[1] > 0) {
                                        var colwidth = Math.floor(100/rowcol[0]);
                                        var html = "<table style='border-collapse: collapse'>";
                                        var cellText = '&nbsp;';
                                        if (this.showCellLocationText){ cellText = this.cellLocationText; }
                                        for (var row = 0; row < rowcol[0]; row++) {
                                            html += "<tr>";
                                            for (var col = 0; col < rowcol[1]; col++) {
                                                html += "<td width='" + colwidth + "%' style='border: " + border + ";'>" + String.format(cellText, (row+1), String.fromCharCode(col+65)) + "</td>";
                                            }
                                            html += "</tr>";
                                        }
                                        html += "</table>";
                                        this.cmp.insertAtCursor(html);
                                    }
                                    this.tableWindow.hide();
                                }else{
                                    if (!frm.findField('row').isValid()){
                                        frm.findField('row').getEl().frame();
                                    }else if (!frm.findField('col').isValid()){
                                        frm.findField('col').getEl().frame();
                                    }
                                }
                            },
                            scope: this
                        }, {
                            text: this.langCancel,
                            handler: function(){
                                this.tableWindow.hide();
                            },
                            scope: this
                        }]
                    });
                
                }else{
                    this.tableWindow.getEl().frame();
                }
                this.tableWindow.show();
            },
            scope: this,
            tooltip: {
                title: this.langTitle
            },
            overflowText: this.langTitle
        });
    }
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @contributor vizcano - http://www.extjs.com/forum/member.php?u=23512
 * @class Ext.ux.form.HtmlEditor.UndoRedo
 * @extends Ext.ux.form.HtmlEditor.MidasCommand
 * <p>A plugin that creates undo and redo buttons on the HtmlEditor. Incomplete.</p>
 */

Ext.ux.form.HtmlEditor.UndoRedo = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    // private
    midasBtns: ['|', {
        cmd: 'undo',
        tooltip: {
            title: 'Undo'
        },
        overflowText: 'Undo'
    }, {
        cmd: 'redo',
        tooltip: {
            title: 'Redo'
        },
        overflowText: 'Redo'
    }]
});

/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.Word
 * @extends Ext.util.Observable
 * <p>A plugin that creates a button on the HtmlEditor for pasting text from Word without all the jibberish html.</p>
 */
Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {
    // Word language text
    langTitle: 'Word Paste',
    langToolTip: 'Cleanse text pasted from Word or other Rich Text applications',
    wordPasteEnabled: true,
    // private
	curLength: 0,
	lastLength: 0,
	lastValue: '',
	// private
    init: function(cmp){
        
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
		this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
        
    },
	// private
	onInit: function(){
		
		Ext.EventManager.on(this.cmp.getDoc(), {
            'keyup': this.checkIfPaste,
            scope: this
        });
		this.lastValue = this.cmp.getValue();
		this.curLength = this.lastValue.length;
		this.lastLength = this.lastValue.length;
		
	},
	// private
	checkIfPaste: function(e){
		
		var diffAt = 0;
		this.curLength = this.cmp.getValue().length;
		
		if (e.V == e.getKey() && e.ctrlKey && this.wordPasteEnabled){
			
			this.cmp.suspendEvents();
			
			diffAt = this.findValueDiffAt(this.cmp.getValue());
			var parts = [
				this.cmp.getValue().substr(0, diffAt),
				this.fixWordPaste(this.cmp.getValue().substr(diffAt, (this.curLength - this.lastLength))),
				this.cmp.getValue().substr((this.curLength - this.lastLength)+diffAt, this.curLength)
			];
			this.cmp.setValue(parts.join(''));
			
			this.cmp.resumeEvents();
		}
		
		this.lastLength = this.cmp.getValue().length;
		this.lastValue = this.cmp.getValue();
		
	},
	// private
	findValueDiffAt: function(val){
		
		for (i=0;i<this.curLength;i++){
			if (this.lastValue[i] != val[i]){
				return i;			
			}
		}
		
	},
    /**
     * Cleans up the jubberish html from Word pasted text.
     * @param wordPaste String The text that needs to be cleansed of Word jibberish html.
     * @return {String} The passed in text with all Word jibberish html removed.
     */
    fixWordPaste: function(wordPaste) {
        
        var removals = [/&nbsp;/ig, /[\r\n]/g, /<(xml|style)[^>]*>.*?<\/\1>/ig, /<\/?(meta|object|span)[^>]*>/ig,
			/<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig, /(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig, /style=(\'\'|\"\")/ig, /<![\[-].*?-*>/g, 
			/MsoNormal/g, /<\\?\?xml[^>]*>/g, /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /&nbsp;/g, 
            /<\/?SPAN[^>]*>/g, /<\/?FONT[^>]*>/g, /<\/?STRONG[^>]*>/g, /<\/?H1[^>]*>/g, /<\/?H2[^>]*>/g, /<\/?H3[^>]*>/g, /<\/?H4[^>]*>/g, 
            /<\/?H5[^>]*>/g, /<\/?H6[^>]*>/g, /<\/?P[^>]*><\/P>/g, /<!--(.*)-->/g, /<!--(.*)>/g, /<!(.*)-->/g, /<\\?\?xml[^>]*>/g, 
            /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /style=\"[^\"]*\"/g, /style=\'[^\"]*\'/g, /lang=\"[^\"]*\"/g, 
            /lang=\'[^\"]*\'/g, /class=\"[^\"]*\"/g, /class=\'[^\"]*\'/g, /type=\"[^\"]*\"/g, /type=\'[^\"]*\'/g, /href=\'#[^\"]*\'/g, 
            /href=\"#[^\"]*\"/g, /name=\"[^\"]*\"/g, /name=\'[^\"]*\'/g, / clear=\"all\"/g, /id=\"[^\"]*\"/g, /title=\"[^\"]*\"/g, 
            /<span[^>]*>/g, /<\/?span[^>]*>/g, /<title>(.*)<\/title>/g, /class=/g, /<meta[^>]*>/g, /<link[^>]*>/g, /<style>(.*)<\/style>/g, 
            /<w:[^>]*>(.*)<\/w:[^>]*>/g];
					
        Ext.each(removals, function(s){
            wordPaste = wordPaste.replace(s, "");
        });
        
        // keep the divs in paragraphs
        wordPaste = wordPaste.replace(/<div[^>]*>/g, "<p>");
        wordPaste = wordPaste.replace(/<\/?div[^>]*>/g, "</p>");
        return wordPaste;
        
    },
	// private
    onRender: function() {
        
        this.cmp.getToolbar().add({
            iconCls: 'x-edit-wordpaste',
            pressed: true,
            handler: function(t){
                t.toggle(!t.pressed);
                this.wordPasteEnabled = !this.wordPasteEnabled;
            },
            scope: this,
            tooltip: {
                text: this.langToolTip
            },
            overflowText: this.langTitle
        });
		
    }
});
Ext.namespace('Ext.ux.form');
/**
 * <p>SuperBoxSelect is an extension of the ComboBox component that displays selected items as labelled boxes within the form field. As seen on facebook, hotmail and other sites.</p>
 * <p>The SuperBoxSelect component was inspired by the BoxSelect component found here: http://efattal.fr/en/extjs/extuxboxselect/</p>
 * 
 * @author <a href="mailto:dan.humphrey@technomedia.co.uk">Dan Humphrey</a>
 * @class Ext.ux.form.SuperBoxSelect
 * @extends Ext.form.ComboBox
 * @constructor
 * @component
 * @version 1.0
 * @license TBA (To be announced)
 * 
 */
Ext.ux.form.SuperBoxSelect = function(config) {
    Ext.ux.form.SuperBoxSelect.superclass.constructor.call(this,config);
    this.addEvents(
        /**
         * Fires before an item is added to the component via user interaction. Return false from the callback function to prevent the item from being added.
         * @event beforeadditem
         * @memberOf Ext.ux.form.SuperBoxSelect
         * @param {SuperBoxSelect} this
         * @param {Mixed} value The value of the item to be added
         */
        'beforeadditem',

        /**
         * Fires after a new item is added to the component.
         * @event additem
         * @memberOf Ext.ux.form.SuperBoxSelect
         * @param {SuperBoxSelect} this
         * @param {Mixed} value The value of the item which was added
         * @param {Record} record The store record which was added
         */
        'additem',

        /**
         * Fires when the allowAddNewData config is set to true, and a user attempts to add an item that is not in the data store.
         * @event newitem
         * @memberOf Ext.ux.form.SuperBoxSelect
         * @param {SuperBoxSelect} this
         * @param {Mixed} value The new item's value
         */
        'newitem',

        /**
         * Fires when an item's remove button is clicked. Return false from the callback function to prevent the item from being removed.
         * @event beforeremoveitem
         * @memberOf Ext.ux.form.SuperBoxSelect
         * @param {SuperBoxSelect} this
         * @param {Mixed} value The value of the item to be removed
         */
        'beforeremoveitem',

        /**
         * Fires after an item has been removed.
         * @event removeitem
         * @memberOf Ext.ux.form.SuperBoxSelect
         * @param {SuperBoxSelect} this
         * @param {Mixed} value The value of the item which was removed
         * @param {Record} record The store record which was removed
         */
        'removeitem',
        /**
         * Fires after the component values have been cleared.
         * @event clear
         * @memberOf Ext.ux.form.SuperBoxSelect
         * @param {SuperBoxSelect} this
         */
        'clear'
    );
    
};
/**
 * @private hide from doc gen
 */
Ext.ux.form.SuperBoxSelect = Ext.extend(Ext.ux.form.SuperBoxSelect,Ext.form.ComboBox,{
    /**
     * @cfg {Boolean} allowAddNewData When set to true, allows items to be added (via the setValueEx and addItem methods) that do not already exist in the data store. Defaults to false.
     */
    allowAddNewData: false,

    /**
     * @cfg {Boolean} backspaceDeletesLastItem When set to false, the BACKSPACE key will focus the last selected item. When set to true, the last item will be immediately deleted. Defaults to true.
     */
    backspaceDeletesLastItem: true,

    /**
     * @cfg {String} classField The underlying data field that will be used to supply an additional class to each item.
     */
    classField: null,

    /**
     * @cfg {String} clearBtnCls An additional class to add to the in-field clear button.
     */
    clearBtnCls: '',

    /**
     * @cfg {String/XTemplate} displayFieldTpl A template for rendering the displayField in each selected item. Defaults to null.
     */
    displayFieldTpl: null,

    /**
     * @cfg {String} extraItemCls An additional css class to apply to each item.
     */
    extraItemCls: '',

    /**
     * @cfg {String/Object/Function} extraItemStyle Additional css style(s) to apply to each item. Should be a valid argument to Ext.Element.applyStyles.
     */
    extraItemStyle: '',

    /**
     * @cfg {String} expandBtnCls An additional class to add to the in-field expand button.
     */
    expandBtnCls: '',

    /**
     * @cfg {Boolean} fixFocusOnTabSelect When set to true, the component will not lose focus when a list item is selected with the TAB key. Defaults to true.
     */
    fixFocusOnTabSelect: true,
    
     /**
     * @cfg {Boolean} forceFormValue When set to true, the component will always return a value to the parent form getValues method, and when the parent form is submitted manually. Defaults to false, meaning the component will only be included in the parent form submission (or getValues) if at least 1 item has been selected.  
     */
    forceFormValue: true,
    /**
     * @cfg {Number} itemDelimiterKey The key code which terminates keying in of individual items, and adds the current
     * item to the list. Defaults to the ENTER key.
     */
    itemDelimiterKey: Ext.EventObject.ENTER,    
    /**
     * @cfg {Boolean} navigateItemsWithTab When set to true the tab key will navigate between selected items. Defaults to true.
     */
    navigateItemsWithTab: true,

    /**
     * @cfg {Boolean} pinList When set to true the select list will be pinned to allow for multiple selections. Defaults to true.
     */
    pinList: true,

    /**
     * @cfg {Boolean} preventDuplicates When set to true unique item values will be enforced. Defaults to true.
     */
    preventDuplicates: true,
    
    /**
     * @cfg {String} queryValuesDelimiter Used to delimit multiple values queried from the server when mode is remote.
     */
    queryValuesDelimiter: '|',
    
    /**
     * @cfg {String} queryValuesIndicator A request variable that is sent to the server (as true) to indicate that we are querying values rather than display data (as used in autocomplete) when mode is remote.
     */
    queryValuesIndicator: 'valuesqry',

    /**
     * @cfg {Boolean} removeValuesFromStore When set to true, selected records will be removed from the store. Defaults to true.
     */
    removeValuesFromStore: true,

    /**
     * @cfg {String} renderFieldBtns When set to true, will render in-field buttons for clearing the component, and displaying the list for selection. Defaults to true.
     */
    renderFieldBtns: true,

    /**
     * @cfg {Boolean} stackItems When set to true, the items will be stacked 1 per line. Defaults to false which displays the items inline.
     */
    stackItems: false,

    /**
     * @cfg {String} styleField The underlying data field that will be used to supply additional css styles to each item.
     */
    styleField : null,
    
     /**
     * @cfg {Boolean} supressClearValueRemoveEvents When true, the removeitem event will not be fired for each item when the clearValue method is called, or when the clear button is used. Defaults to false.
     */
    supressClearValueRemoveEvents : false,
    
    /**
     * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable automatic validation (defaults to 'blur').
     */
	validationEvent : 'blur',
	
    /**
     * @cfg {String} valueDelimiter The delimiter to use when joining and splitting value arrays and strings.
     */
    valueDelimiter: ',',
    initComponent:function() {
       Ext.apply(this, {
            items           : new Ext.util.MixedCollection(false),
            usedRecords     : new Ext.util.MixedCollection(false),
            addedRecords	: [],
            remoteLookup	: [],
            hideTrigger     : true,
            grow            : false,
            resizable       : false,
            multiSelectMode : false,
            preRenderValue  : null
        });
        
        if(this.transform){
            this.doTransform();
        }
        if(this.forceFormValue){
        	this.items.on({
        	   add: this.manageNameAttribute,
        	   remove: this.manageNameAttribute,
        	   clear: this.manageNameAttribute,
        	   scope: this
        	});
        }
        
        Ext.ux.form.SuperBoxSelect.superclass.initComponent.call(this);
        if(this.mode === 'remote' && this.store){
        	this.store.on('load', this.onStoreLoad, this);
        }
    },
    onRender:function(ct, position) {
    	var h = this.hiddenName;
    	this.hiddenName = null;
        Ext.ux.form.SuperBoxSelect.superclass.onRender.call(this, ct, position);
        this.hiddenName = h;
        this.manageNameAttribute();
       
        var extraClass = (this.stackItems === true) ? 'x-superboxselect-stacked' : '';
        if(this.renderFieldBtns){
            extraClass += ' x-superboxselect-display-btns';
        }
        this.el.removeClass('x-form-text').addClass('x-superboxselect-input-field');
        
        this.wrapEl = this.el.wrap({
            tag : 'ul'
        });
        
        this.outerWrapEl = this.wrapEl.wrap({
            tag : 'div',
            cls: 'x-form-text x-superboxselect ' + extraClass
        });
       
        this.inputEl = this.el.wrap({
            tag : 'li',
            cls : 'x-superboxselect-input'
        });
        
        if(this.renderFieldBtns){
            this.setupFieldButtons().manageClearBtn();
        }
        
        this.setupFormInterception();
    },
    onStoreLoad : function(store, records, options){
    	//accomodating for bug in Ext 3.0.0 where options.params are empty
    	var q = options.params[this.queryParam] || store.baseParams[this.queryParam] || "",
    		isValuesQuery = options.params[this.queryValuesIndicator] || store.baseParams[this.queryValuesIndicator];
    	
    	if(this.removeValuesFromStore){
    		this.store.each(function(record) {
				if(this.usedRecords.containsKey(record.get(this.valueField))){
					this.store.remove(record);
				}
			}, this);
    	}
    	//queried values
    	if(isValuesQuery){
    		var params = q.split(this.queryValuesDelimiter);
    		Ext.each(params,function(p){
    			this.remoteLookup.remove(p);
    			var rec = this.findRecord(this.valueField,p);
    			if(rec){
    				this.addRecord(rec);
    			}
    		},this);
    		
    		if(this.setOriginal){
    			this.setOriginal = false;
    			this.originalValue = this.getValue();
    		}
    	}

    	//queried display (autocomplete) & addItem
    	if(q !== '' && this.allowAddNewData){
    		Ext.each(this.remoteLookup,function(r){
    			if(typeof r == "object" && r[this.displayField] == q){
    				this.remoteLookup.remove(r);
					if(records.length && records[0].get(this.displayField) === q) {
						this.addRecord(records[0]);
						return;
					}
					var rec = this.createRecord(r);
					this.store.add(rec);
		        	this.addRecord(rec);
		        	this.addedRecords.push(rec); //keep track of records added to store
		        	(function(){
		        		if(this.isExpanded()){
			        		this.collapse();
		        		}
		        	}).defer(10,this);
		        	return;
    			}
    		},this);
    	}
    	
    	var toAdd = [];
    	if(q === ''){
	    	Ext.each(this.addedRecords,function(rec){
	    		if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){
					return;	    			
	    		}
	    		toAdd.push(rec);
	    		
	    	},this);
	    	
    	}else{
    		var re = new RegExp(Ext.escapeRe(q) + '.*','i');
    		Ext.each(this.addedRecords,function(rec){
	    		if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){
					return;	    			
	    		}
	    		if(re.test(rec.get(this.displayField))){
	    			toAdd.push(rec);
	    		}
	    	},this);
	    }
    	this.store.add(toAdd);
    	this.store.sort(this.displayField, 'ASC');
    	
		if(this.store.getCount() === 0 && this.isExpanded()){
			this.collapse();
		}
		
	},
    doTransform : function() {
    	var s = Ext.getDom(this.transform), transformValues = [];
            if(!this.store){
                this.mode = 'local';
                var d = [], opts = s.options;
                for(var i = 0, len = opts.length;i < len; i++){
                    var o = opts[i], oe = Ext.get(o),
                        value = oe.getAttributeNS(null,'value') || '',
                        cls = oe.getAttributeNS(null,'className') || '',
                        style = oe.getAttributeNS(null,'style') || '';
                    if(o.selected) {
                        transformValues.push(value);
                    }
                    d.push([value, o.text, cls, typeof(style) === "string" ? style : style.cssText]);
                }
                this.store = new Ext.data.SimpleStore({
                    'id': 0,
                    fields: ['value', 'text', 'cls', 'style'],
                    data : d
                });
                Ext.apply(this,{
                    valueField: 'value',
                    displayField: 'text',
                    classField: 'cls',
                    styleField: 'style'
                });
            }
           
            if(transformValues.length){
                this.value = transformValues.join(',');
            }
    },
    setupFieldButtons : function(){
        this.buttonWrap = this.outerWrapEl.createChild({
            cls: 'x-superboxselect-btns'
        });
        
        this.buttonClear = this.buttonWrap.createChild({
            tag:'div',
            cls: 'x-superboxselect-btn-clear ' + this.clearBtnCls
        });
        
        this.buttonExpand = this.buttonWrap.createChild({
            tag:'div',
            cls: 'x-superboxselect-btn-expand ' + this.expandBtnCls
        });
        
        this.initButtonEvents();
        
        return this;
    },
    initButtonEvents : function() {
        this.buttonClear.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {
            e.stopEvent();
            if (this.disabled) {
                return;
            }
            this.clearValue();
            this.el.focus();
        }, this);

        this.buttonExpand.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {
            e.stopEvent();
            if (this.disabled) {
                return;
            }
            if (this.isExpanded()) {
                this.multiSelectMode = false;
            } else if (this.pinList) {
                this.multiSelectMode = true;
            }
            this.onTriggerClick();
        }, this);
    },
    removeButtonEvents : function() {
        this.buttonClear.removeAllListeners();
        this.buttonExpand.removeAllListeners();
        return this;
    },
    clearCurrentFocus : function(){
        if(this.currentFocus){
            this.currentFocus.onLnkBlur();
            this.currentFocus = null;
        }  
        return this;        
    },
    initEvents : function() {
        var el = this.el;

        el.on({
            click   : this.onClick,
            focus   : this.clearCurrentFocus,
            blur    : this.onBlur,

            keydown : this.onKeyDownHandler,
            keyup   : this.onKeyUpBuffered,

            scope   : this
        });

        this.on({
            collapse: this.onCollapse,
            expand: this.clearCurrentFocus,
            scope: this
        });

        this.wrapEl.on('click', this.onWrapClick, this);
        this.outerWrapEl.on('click', this.onWrapClick, this);
        
        this.inputEl.focus = function() {
            el.focus();
        };

        Ext.ux.form.SuperBoxSelect.superclass.initEvents.call(this);

        Ext.apply(this.keyNav, {
            tab: function(e) {
                if (this.fixFocusOnTabSelect && this.isExpanded()) {
                    e.stopEvent();
                    el.blur();
                    this.onViewClick(false);
                    this.focus(false, 10);
                    return true;
                }

                this.onViewClick(false);
                if (el.dom.value !== '') {
                    this.setRawValue('');
                }

                return true;
            },

            down: function(e) {
                if (!this.isExpanded() && !this.currentFocus) {
                    this.onTriggerClick();
                } else {
                    this.inKeyMode = true;
                    this.selectNext();
                }
            },

            enter: function(){}
        });
    },

    onClick: function() {
        this.clearCurrentFocus();
        this.collapse();
        this.autoSize();
    },

    beforeBlur: Ext.form.ComboBox.superclass.beforeBlur,

    onFocus: function() {
        this.outerWrapEl.addClass(this.focusClass);

        Ext.ux.form.SuperBoxSelect.superclass.onFocus.call(this);
    },

    onBlur: function() {
        this.outerWrapEl.removeClass(this.focusClass);

        this.clearCurrentFocus();

        if (this.el.dom.value !== '') {
            this.applyEmptyText();
            this.autoSize();
        }

        Ext.ux.form.SuperBoxSelect.superclass.onBlur.call(this);
    },

    onCollapse: function() {
    	this.view.clearSelections();
        this.multiSelectMode = false;
    },

    onWrapClick: function(e) {
        e.stopEvent();
        this.collapse();
        this.el.focus();
        this.clearCurrentFocus();
    },
    markInvalid : function(msg) {
        var elp, t;

        if (!this.rendered || this.preventMark ) {
            return;
        }
        this.outerWrapEl.addClass(this.invalidClass);
        msg = msg || this.invalidText;

        switch (this.msgTarget) {
            case 'qtip':
                Ext.apply(this.el.dom, {
                    qtip    : msg,
                    qclass  : 'x-form-invalid-tip'
                });
                Ext.apply(this.wrapEl.dom, {
                    qtip    : msg,
                    qclass  : 'x-form-invalid-tip'
                });
                if (Ext.QuickTips) { // fix for floating editors interacting with DND
                    Ext.QuickTips.enable();
                }
                break;
            case 'title':
                this.el.dom.title = msg;
                this.wrapEl.dom.title = msg;
                this.outerWrapEl.dom.title = msg;
                break;
            case 'under':
                if (!this.errorEl) {
                    elp = this.getErrorCt();
                    if (!elp) { // field has no container el
                        this.el.dom.title = msg;
                        break;
                    }
                    this.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
                    this.errorEl.setWidth(elp.getWidth(true) - 20);
                }
                this.errorEl.update(msg);
                Ext.form.Field.msgFx[this.msgFx].show(this.errorEl, this);
                break;
            case 'side':
                if (!this.errorIcon) {
                    elp = this.getErrorCt();
                    if (!elp) { // field has no container el
                        this.el.dom.title = msg;
                        break;
                    }
                    this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
                }
                this.alignErrorIcon();
                Ext.apply(this.errorIcon.dom, {
                    qtip    : msg,
                    qclass  : 'x-form-invalid-tip'
                });
                this.errorIcon.show();
                this.on('resize', this.alignErrorIcon, this);
                break;
            default:
                t = Ext.getDom(this.msgTarget);
                t.innerHTML = msg;
                t.style.display = this.msgDisplay;
                break;
        }
        this.fireEvent('invalid', this, msg);
    },
    clearInvalid : function(){
        if(!this.rendered || this.preventMark){ // not rendered
            return;
        }
        this.outerWrapEl.removeClass(this.invalidClass);
        switch(this.msgTarget){
            case 'qtip':
                this.el.dom.qtip = '';
                this.wrapEl.dom.qtip ='';
                break;
            case 'title':
                this.el.dom.title = '';
                this.wrapEl.dom.title = '';
                this.outerWrapEl.dom.title = '';
                break;
            case 'under':
                if(this.errorEl){
                    Ext.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);
                }
                break;
            case 'side':
                if(this.errorIcon){
                    this.errorIcon.dom.qtip = '';
                    this.errorIcon.hide();
                    this.un('resize', this.alignErrorIcon, this);
                }
                break;
            default:
                var t = Ext.getDom(this.msgTarget);
                t.innerHTML = '';
                t.style.display = 'none';
                break;
        }
        this.fireEvent('valid', this);
    },
    alignErrorIcon : function(){
        if(this.wrap){
            this.errorIcon.alignTo(this.wrap, 'tl-tr', [Ext.isIE ? 5 : 2, 3]);
        }
    },
    expand : function(){
        if (this.isExpanded() || !this.hasFocus) {
            return;
        }
        this.list.alignTo(this.outerWrapEl, this.listAlign).show();
        this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
        Ext.getDoc().on({
            mousewheel: this.collapseIf,
            mousedown: this.collapseIf,
            scope: this
        });
        this.fireEvent('expand', this);
    },
    restrictHeight : function(){
        var inner = this.innerList.dom,
            st = inner.scrollTop, 
            list = this.list;
        
        inner.style.height = '';
        
        var pad = list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight,
            h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
            ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
            hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
            space = Math.max(ha, hb, this.minHeight || 0)-list.shadowOffset-pad-5;
        
        h = Math.min(h, space, this.maxHeight);
        this.innerList.setHeight(h);

        list.beginUpdate();
        list.setHeight(h+pad);
        list.alignTo(this.outerWrapEl, this.listAlign);
        list.endUpdate();
        
        if(this.multiSelectMode){
            inner.scrollTop = st;
        }
    },
    
    validateValue: function(val){
        if(this.items.getCount() === 0){
             if(this.allowBlank){
                 this.clearInvalid();
                 return true;
             }else{
                 this.markInvalid(this.blankText);
                 return false;
             }
        }
        
        this.clearInvalid();
        return true;
    },

    manageNameAttribute :  function(){
    	if(this.items.getCount() === 0 && this.forceFormValue){
    	   this.el.dom.setAttribute('name', this.hiddenName || this.name);
    	}else{
    		this.el.dom.removeAttribute('name');
    	}
    },
    setupFormInterception : function(){
        var form;
        this.findParentBy(function(p){ 
            if(p.getForm){
                form = p.getForm();
            }
        });
        if(form){
        	
        	var formGet = form.getValues;
            form.getValues = function(asString){
                this.el.dom.disabled = true;
                var oldVal = this.el.dom.value;
                this.setRawValue('');
                var vals = formGet.call(form);
                this.el.dom.disabled = false;
                this.setRawValue(oldVal);
                if(this.forceFormValue && this.items.getCount() === 0){
                	vals[this.name] = '';
                }
                return asString ? Ext.urlEncode(vals) : vals ;
            }.createDelegate(this);
        }
    },
    onResize : function(w, h, rw, rh) {
        var reduce = Ext.isIE6 ? 4 : Ext.isIE7 ? 1 : Ext.isIE8 ? 1 : 0;
        if(this.wrapEl){
            this._width = w;
            this.outerWrapEl.setWidth(w - reduce);
            if (this.renderFieldBtns) {
                reduce += (this.buttonWrap.getWidth() + 20);
                this.wrapEl.setWidth(w - reduce);
        }
        }
        Ext.ux.form.SuperBoxSelect.superclass.onResize.call(this, w, h, rw, rh);
        this.autoSize();
    },
    onEnable: function(){
        Ext.ux.form.SuperBoxSelect.superclass.onEnable.call(this);
        this.items.each(function(item){
            item.enable();
        });
        if (this.renderFieldBtns) {
            this.initButtonEvents();
        }
    },
    onDisable: function(){
        Ext.ux.form.SuperBoxSelect.superclass.onDisable.call(this);
        this.items.each(function(item){
            item.disable();
        });
        if(this.renderFieldBtns){
            this.removeButtonEvents();
        }
    },
    /**
     * Clears all values from the component.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name clearValue
     * @param {Boolean} supressRemoveEvent [Optional] When true, the 'removeitem' event will not fire for each item that is removed.    
     */
    clearValue : function(supressRemoveEvent){
        Ext.ux.form.SuperBoxSelect.superclass.clearValue.call(this);
        this.preventMultipleRemoveEvents = supressRemoveEvent || this.supressClearValueRemoveEvents || false;
    	this.removeAllItems();
    	this.preventMultipleRemoveEvents = false;
        this.fireEvent('clear',this);
        return this;
    },
    onKeyUp : function(e) {
        if (this.editable !== false && (!e.isSpecialKey() || e.getKey() === e.BACKSPACE) && e.getKey() !== this.itemDelimiterKey && (!e.hasModifier() || e.shiftKey)) {
            this.lastKey = e.getKey();
            this.dqTask.delay(this.queryDelay);
        }        
    },
    onKeyDownHandler : function(e,t) {
    	    	
        var toDestroy,nextFocus,idx;
        if ((e.getKey() === e.DELETE || e.getKey() === e.SPACE) && this.currentFocus){
            e.stopEvent();
            toDestroy = this.currentFocus;
            this.on('expand',function(){this.collapse();},this,{single: true});
            idx = this.items.indexOfKey(this.currentFocus.key);
            
            this.clearCurrentFocus();
            
            if(idx < (this.items.getCount() -1)){
                nextFocus = this.items.itemAt(idx+1);
            }
            
            toDestroy.preDestroy(true);
            if(nextFocus){
                (function(){
                    nextFocus.onLnkFocus();
                    this.currentFocus = nextFocus;
                }).defer(200,this);
            }
        
            return true;
        }
        
        var val = this.el.dom.value, it, ctrl = e.ctrlKey;
        if(e.getKey() === this.itemDelimiterKey){
            e.stopEvent();
            if (val !== "") {
                if (ctrl || !this.isExpanded())  {  //ctrl+enter for new items
                	this.view.clearSelections();
                    this.collapse();
                    this.setRawValue('');
                    this.fireEvent('newitem', this, val);
                }
                else {
                	this.onViewClick();
                    //removed from 3.0.1
                    if(this.unsetDelayCheck){
                        this.delayedCheck = true;
                        this.unsetDelayCheck.defer(10, this);
                    }
                }
            }else{
                if(!this.isExpanded()){
                    return;
                }
                this.onViewClick();
                //removed from 3.0.1
                if(this.unsetDelayCheck){
                    this.delayedCheck = true;
                    this.unsetDelayCheck.defer(10, this);
                }
            }
            return true;
        }
        
        if(val !== '') {
            this.autoSize();
            return;
        }
        
        //select first item
        if(e.getKey() === e.HOME){
            e.stopEvent();
            if(this.items.getCount() > 0){
                this.collapse();
                it = this.items.get(0);
                it.el.focus();
                
            }
            return true;
        }
        //backspace remove
        if(e.getKey() === e.BACKSPACE){
            e.stopEvent();
            if(this.currentFocus) {
                toDestroy = this.currentFocus;
                this.on('expand',function(){
                    this.collapse();
                },this,{single: true});
                
                idx = this.items.indexOfKey(toDestroy.key);
                
                this.clearCurrentFocus();
                if(idx < (this.items.getCount() -1)){
                    nextFocus = this.items.itemAt(idx+1);
                }
                
                toDestroy.preDestroy(true);
                
                if(nextFocus){
                    (function(){
                        nextFocus.onLnkFocus();
                        this.currentFocus = nextFocus;
                    }).defer(200,this);
                }
                
                return;
            }else{
                it = this.items.get(this.items.getCount() -1);
                if(it){
                    if(this.backspaceDeletesLastItem){
                        this.on('expand',function(){this.collapse();},this,{single: true});
                        it.preDestroy(true);
                    }else{
                        if(this.navigateItemsWithTab){
                            it.onElClick();
                        }else{
                            this.on('expand',function(){
                                this.collapse();
                                this.currentFocus = it;
                                this.currentFocus.onLnkFocus.defer(20,this.currentFocus);
                            },this,{single: true});
                        }
                    }
                }
                return true;
            }
        }
        
        if(!e.isNavKeyPress()){
            this.multiSelectMode = false;
            this.clearCurrentFocus();
            return;
        }
        //arrow nav
        if(e.getKey() === e.LEFT || (e.getKey() === e.UP && !this.isExpanded())){
            e.stopEvent();
            this.collapse();
            //get last item
            it = this.items.get(this.items.getCount()-1);
            if(this.navigateItemsWithTab){ 
                //focus last el
                if(it){
                    it.focus(); 
                }
            }else{
                //focus prev item
                if(this.currentFocus){
                    idx = this.items.indexOfKey(this.currentFocus.key);
                    this.clearCurrentFocus();
                    
                    if(idx !== 0){
                        this.currentFocus = this.items.itemAt(idx-1);
                        this.currentFocus.onLnkFocus();
                    }
                }else{
                    this.currentFocus = it;
                    if(it){
                        it.onLnkFocus();
                    }
                }
            }
            return true;
        }
        if(e.getKey() === e.DOWN){
            if(this.currentFocus){
                this.collapse();
                e.stopEvent();
                idx = this.items.indexOfKey(this.currentFocus.key);
                if(idx == (this.items.getCount() -1)){
                    this.clearCurrentFocus.defer(10,this);
                }else{
                    this.clearCurrentFocus();
                    this.currentFocus = this.items.itemAt(idx+1);
                    if(this.currentFocus){
                        this.currentFocus.onLnkFocus();
                    }
                }
                return true;
            }
        }
        if(e.getKey() === e.RIGHT){
            this.collapse();
            it = this.items.itemAt(0);
            if(this.navigateItemsWithTab){ 
                //focus first el
                if(it){
                    it.focus(); 
                }
            }else{
                if(this.currentFocus){
                    idx = this.items.indexOfKey(this.currentFocus.key);
                    this.clearCurrentFocus();
                    if(idx < (this.items.getCount() -1)){
                        this.currentFocus = this.items.itemAt(idx+1);
                        if(this.currentFocus){
                            this.currentFocus.onLnkFocus();
                        }
                    }
                }else{
                    this.currentFocus = it;
                    if(it){
                        it.onLnkFocus();
                    }
                }
            }
        }
    },
    onKeyUpBuffered : function(e){
        if(!e.isNavKeyPress()){
            this.autoSize();
        }
    },
    reset :  function(){
    	this.killItems();
        Ext.ux.form.SuperBoxSelect.superclass.reset.call(this);
        this.addedRecords = [];
        this.autoSize().setRawValue('');
    },
    applyEmptyText : function(){
		this.setRawValue('');
        if(this.items.getCount() > 0){
            this.el.removeClass(this.emptyClass);
            this.setRawValue('');
            return this;
        }
        if(this.rendered && this.emptyText && this.getRawValue().length < 1){
            this.setRawValue(this.emptyText);
            this.el.addClass(this.emptyClass);
        }
        return this;
    },
    /**
     * @private
     * 
     * Use clearValue instead
     */
    removeAllItems: function(){
    	this.items.each(function(item){
            item.preDestroy(true);
        },this);
        this.manageClearBtn();
        return this;
    },
    killItems : function(){
    	this.items.each(function(item){
            item.kill();
        },this);
        this.resetStore();
        this.items.clear();
        this.manageClearBtn();
        return this;
    },
    resetStore: function(){
        this.store.clearFilter();
        if(!this.removeValuesFromStore){
            return this;
        }
        this.usedRecords.each(function(rec){
            this.store.add(rec);
        },this);
        this.usedRecords.clear();
        this.sortStore();
        return this;
    },
    sortStore: function(){
        var ss = this.store.getSortState();
        if(ss && ss.field){
            this.store.sort(ss.field, ss.direction);
        }
        return this;
    },
    getCaption: function(dataObject){
        if(typeof this.displayFieldTpl === 'string') {
            this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl);
        }
        var caption, recordData = dataObject instanceof Ext.data.Record ? dataObject.data : dataObject;
      
        if(this.displayFieldTpl) {
            caption = this.displayFieldTpl.apply(recordData);
        } else if(this.displayField) {
            caption = recordData[this.displayField];
        }
        
        return caption;
    },
    addRecord : function(record) {
        var display = record.data[this.displayField],
            caption = this.getCaption(record),
            val = record.data[this.valueField],
            cls = this.classField ? record.data[this.classField] : '',
            style = this.styleField ? record.data[this.styleField] : '';

        if (this.removeValuesFromStore) {
            this.usedRecords.add(val, record);
            this.store.remove(record);
        }
        
        this.addItemBox(val, display, caption, cls, style);
        this.fireEvent('additem', this, val, record);
    },
    createRecord : function(recordData){
        if(!this.recordConstructor){
            var recordFields = [
                {name: this.valueField},
                {name: this.displayField}
            ];
            if(this.classField){
                recordFields.push({name: this.classField});
            }
            if(this.styleField){
                recordFields.push({name: this.styleField});
            }
            this.recordConstructor = Ext.data.Record.create(recordFields);
        }
        return new this.recordConstructor(recordData);
    },
    /**
     * Adds an array of items to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name addItem
     * @param {Array} newItemObjects An Array of object literals containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} 
     */
    addItems : function(newItemObjects){
    	if (Ext.isArray(newItemObjects)) {
			Ext.each(newItemObjects, function(item) {
				this.addItem(item);
			}, this);
		} else {
			this.addItem(newItemObjects);
		}
    },
    /**
     * Adds a new non-existing item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
     * This method should be used in place of addItem from within the newitem event handler.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name addNewItem
     * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} 
     */
    addNewItem : function(newItemObject){
    	this.addItem(newItemObject,true);
    },
    /**
     * Adds an item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name addItem
     * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} 
     */
    addItem : function(newItemObject, /*hidden param*/ forcedAdd){
        
        var val = newItemObject[this.valueField];

        if(this.disabled) {
            return false;
        }
        if(this.preventDuplicates && this.hasValue(val)){
            return;
        }
        
        //use existing record if found
        var record = this.findRecord(this.valueField, val);
        if (record) {
            this.addRecord(record);
            return;
        } else if (!this.allowAddNewData) { // else it's a new item
            return;
        }
        
        if(this.mode === 'remote'){
        	this.remoteLookup.push(newItemObject); 
        	this.doQuery(val,false,false,forcedAdd);
        	return;
        }
        
        var rec = this.createRecord(newItemObject);
        this.store.add(rec);
        this.addRecord(rec);
        
        return true;
    },
    addItemBox : function(itemVal,itemDisplay,itemCaption, itemClass, itemStyle) {
        var hConfig, parseStyle = function(s){
            var ret = '';
            if(typeof s == 'function'){
                ret = s.call();
            }else if(typeof s == 'object'){
                for(var p in s){
                    ret+= p +':'+s[p]+';';
                }
            }else if(typeof s == 'string'){
                ret = s + ';';
            }
            return ret;
        }, itemKey = Ext.id(null,'sbx-item'), box = new Ext.ux.form.SuperBoxSelectItem({
            owner: this,
            disabled: this.disabled,
            renderTo: this.wrapEl,
            cls: this.extraItemCls + ' ' + itemClass,
            style: parseStyle(this.extraItemStyle) + ' ' + itemStyle,
            caption: itemCaption,
            display: itemDisplay,
            value:  itemVal,
            key: itemKey,
            listeners: {
                'remove': function(item){
                    if(this.fireEvent('beforeremoveitem',this,item.value) === false){
                        return;
                    }
                    this.items.removeKey(item.key);
                    if(this.removeValuesFromStore){
                        if(this.usedRecords.containsKey(item.value)){
                            this.store.add(this.usedRecords.get(item.value));
                            this.usedRecords.removeKey(item.value);
                            this.sortStore();
                            if(this.view){
                                this.view.render();
                            }
                        }
                    }
                    if(!this.preventMultipleRemoveEvents){
                    	this.fireEvent.defer(250,this,['removeitem',this,item.value, this.findInStore(item.value)]);
                    }
                },
                destroy: function(){
                    this.collapse();
                    this.autoSize().manageClearBtn().validateValue();
                },
                scope: this
            }
        });
        box.render();
        
        hConfig = {
            tag :'input', 
            type :'hidden', 
            value : itemVal,
            name : (this.hiddenName || this.name)
        };
        
        if(this.disabled){
        	Ext.apply(hConfig,{
        	   disabled : 'disabled'
        	})
        }
        box.hidden = this.el.insertSibling(hConfig,'before');

        this.items.add(itemKey,box);
        this.applyEmptyText().autoSize().manageClearBtn().validateValue();
    },
    manageClearBtn : function() {
        if (!this.renderFieldBtns || !this.rendered) {
            return this;
        }
        var cls = 'x-superboxselect-btn-hide';
        if (this.items.getCount() === 0) {
            this.buttonClear.addClass(cls);
        } else {
            this.buttonClear.removeClass(cls);
        }
        return this;
    },
    findInStore : function(val){
        var index = this.store.find(this.valueField, val);
        if(index > -1){
            return this.store.getAt(index);
        }
        return false;
    },
    /**
     * Returns a String value containing a concatenated list of item values. The list is concatenated with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter}.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name getValue
     * @return {String} a String value containing a concatenated list of item values. 
     */
    getValue : function() {
        var ret = [];
        this.items.each(function(item){
            ret.push(item.value);
        });
        return ret.join(this.valueDelimiter);
    },
    /**
     * Returns an Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField}, {@link #Ext.ux.form.SuperBoxSelect-classField} and {@link #Ext.ux.form.SuperBoxSelect-styleField} properties.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name getValueEx
     * @return {Array} an array of item objects. 
     */
    getValueEx : function() {
        var ret = [];
        this.items.each(function(item){
            var newItem = {};
            newItem[this.valueField] = item.value;
            newItem[this.displayField] = item.display;
            if(this.classField){
                newItem[this.classField] = item.cls || '';
            }
            if(this.styleField){
                newItem[this.styleField] = item.style || '';
            }
            ret.push(newItem);
        },this);
        return ret;
    },
    // private
    initValue : function(){
 
        Ext.ux.form.SuperBoxSelect.superclass.initValue.call(this);
        if(this.mode === 'remote') {
        	this.setOriginal = true;
        }
    },
    /**
     * Sets the value of the SuperBoxSelect component.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name setValue
     * @param {String|Array} value An array of item values, or a String value containing a delimited list of item values. (The list should be delimited with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter) 
     */
    setValue : function(value){
        if(!this.rendered){
            this.value = value;
            return;
        }
            
        this.removeAllItems().resetStore();
        this.remoteLookup = [];
        
        if(Ext.isEmpty(value)){
        	return;
        }
        
        var values = value;
        if(!Ext.isArray(value)){
            value = '' + value;
            values = value.split(this.valueDelimiter); 
        }
        
        Ext.each(values,function(val){
            var record = this.findRecord(this.valueField, val);
            if(record){
                this.addRecord(record);
            }else if(this.mode === 'remote'){
				this.remoteLookup.push(val);            	
            }
        },this);
        
        if(this.mode === 'remote'){
      		var q = this.remoteLookup.join(this.queryValuesDelimiter); 
      		this.doQuery(q,false, true); //3rd param to specify a values query
        }
        
    },
    /**
     * Sets the value of the SuperBoxSelect component, adding new items that don't exist in the data store if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name setValue
     * @param {Array} data An Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} properties.  
     */
    setValueEx : function(data){
        this.removeAllItems().resetStore();
        
        if(!Ext.isArray(data)){
            data = [data];
        }
        this.remoteLookup = [];
        
        if(this.allowAddNewData && this.mode === 'remote'){ // no need to query
            Ext.each(data, function(d){
            	var r = this.findRecord(this.valueField, d[this.valueField]) || this.createRecord(d);
                this.addRecord(r);
            },this);
            return;
        }
        
        Ext.each(data,function(item){
            this.addItem(item);
        },this);
    },
    /**
     * Returns true if the SuperBoxSelect component has a selected item with a value matching the 'val' parameter.
     * @methodOf Ext.ux.form.SuperBoxSelect
     * @name hasValue
     * @param {Mixed} val The value to test.
     * @return {Boolean} true if the component has the selected value, false otherwise.
     */
    hasValue: function(val){
        var has = false;
        this.items.each(function(item){
            if(item.value == val){
                has = true;
                return false;
            }
        },this);
        return has;
    },
    onSelect : function(record, index) {
    	if (this.fireEvent('beforeselect', this, record, index) !== false){
            var val = record.data[this.valueField];
            
            if(this.preventDuplicates && this.hasValue(val)){
                return;
            }
            
            this.setRawValue('');
            this.lastSelectionText = '';
            
            if(this.fireEvent('beforeadditem',this,val) !== false){
                this.addRecord(record);
            }
            if(this.store.getCount() === 0 || !this.multiSelectMode){
                this.collapse();
            }else{
                this.restrictHeight();
            }
    	}
    },
    onDestroy : function() {
        this.items.purgeListeners();
        this.killItems();
        if (this.renderFieldBtns) {
            Ext.destroy(
                this.buttonClear,
                this.buttonExpand,
                this.buttonWrap
            );
        }

        Ext.destroy(
            this.inputEl,
            this.wrapEl,
            this.outerWrapEl
        );

        Ext.ux.form.SuperBoxSelect.superclass.onDestroy.call(this);
    },
    autoSize : function(){
        if(!this.rendered){
            return this;
        }
        if(!this.metrics){
            this.metrics = Ext.util.TextMetrics.createInstance(this.el);
        }
        var el = this.el,
            v = el.dom.value,
            d = document.createElement('div');

        if(v === "" && this.emptyText && this.items.getCount() < 1){
            v = this.emptyText;
        }
        d.appendChild(document.createTextNode(v));
        v = d.innerHTML;
        d = null;
        v += "&#160;";
        var w = Math.max(this.metrics.getWidth(v) +  24, 24);
        if(typeof this._width != 'undefined'){
            w = Math.min(this._width, w);
        }
        this.el.setWidth(w);
        
        if(Ext.isIE){
            this.el.dom.style.top='0';
        }
        return this;
    },
    doQuery : function(q, forceAll,valuesQuery, forcedAdd){
        q = Ext.isEmpty(q) ? '' : q;
        var qe = {
            query: q,
            forceAll: forceAll,
            combo: this,
            cancel:false
        };
        if(this.fireEvent('beforequery', qe)===false || qe.cancel){
            return false;
        }
        q = qe.query;
        forceAll = qe.forceAll;
        if(forceAll === true || (q.length >= this.minChars) || valuesQuery && !Ext.isEmpty(q)){
            if(this.lastQuery !== q || forcedAdd){
            	this.lastQuery = q;
                if(this.mode == 'local'){
                    this.selectedIndex = -1;
                    if(forceAll){
                        this.store.clearFilter();
                    }else{
                        this.store.filter(this.displayField, q);
                    }
                    this.onLoad();
                }else{
                	
                    this.store.baseParams[this.queryParam] = q;
                    this.store.baseParams[this.queryValuesIndicator] = valuesQuery;
                    this.store.load({
                        params: this.getParams(q)
                    });
                    if(!forcedAdd){
                        this.expand();
                    }
                }
            }else{
                this.selectedIndex = -1;
                this.onLoad();
            }
        }
    }
});
Ext.reg('superboxselect', Ext.ux.form.SuperBoxSelect);
/*
 * @private
 */
Ext.ux.form.SuperBoxSelectItem = function(config){
    Ext.apply(this,config);
    Ext.ux.form.SuperBoxSelectItem.superclass.constructor.call(this); 
};
/*
 * @private
 */
Ext.ux.form.SuperBoxSelectItem = Ext.extend(Ext.ux.form.SuperBoxSelectItem,Ext.Component, {
    initComponent : function(){
        Ext.ux.form.SuperBoxSelectItem.superclass.initComponent.call(this); 
    },
    onElClick : function(e){
        var o = this.owner;
        o.clearCurrentFocus().collapse();
        if(o.navigateItemsWithTab){
            this.focus();
        }else{
            o.el.dom.focus();
            var that = this;
            (function(){
                this.onLnkFocus();
                o.currentFocus = this;
            }).defer(10,this);
        }
    },
    
    onLnkClick : function(e){
        if(e) {
            e.stopEvent();
        }
        this.preDestroy();
        if(!this.owner.navigateItemsWithTab){
            this.owner.el.focus();
        }
    },
    onLnkFocus : function(){
        this.el.addClass("x-superboxselect-item-focus");
        this.owner.outerWrapEl.addClass("x-form-focus");
    },
    
    onLnkBlur : function(){
        this.el.removeClass("x-superboxselect-item-focus");
        this.owner.outerWrapEl.removeClass("x-form-focus");
    },
    
    enableElListeners : function() {
        this.el.on('click', this.onElClick, this, {stopEvent:true});
       
        this.el.addClassOnOver('x-superboxselect-item x-superboxselect-item-hover');
    },

    enableLnkListeners : function() {
        this.lnk.on({
            click: this.onLnkClick,
            focus: this.onLnkFocus,
            blur:  this.onLnkBlur,
            scope: this
        });
    },
    
    enableAllListeners : function() {
        this.enableElListeners();
        this.enableLnkListeners();
    },
    disableAllListeners : function() {
        this.el.removeAllListeners();
        this.lnk.un('click', this.onLnkClick, this);
        this.lnk.un('focus', this.onLnkFocus, this);
        this.lnk.un('blur', this.onLnkBlur, this);
    },
    onRender : function(ct, position){
        
        Ext.ux.form.SuperBoxSelectItem.superclass.onRender.call(this, ct, position);
        
        var el = this.el;
        if(el){
            el.remove();
        }
        
        this.el = el = ct.createChild({ tag: 'li' }, ct.last());
        el.addClass('x-superboxselect-item');
        
        var btnEl = this.owner.navigateItemsWithTab ? ( Ext.isSafari ? 'button' : 'a') : 'span';
        var itemKey = this.key;
        
        Ext.apply(el, {
            focus: function(){
                var c = this.down(btnEl +'.x-superboxselect-item-close');
                if(c){
                	c.focus();
                }
            },
            preDestroy: function(){
                this.preDestroy();
            }.createDelegate(this)
        });
        
        this.enableElListeners();

        el.update(this.caption);

        var cfg = {
            tag: btnEl,
            'class': 'x-superboxselect-item-close',
            tabIndex : this.owner.navigateItemsWithTab ? '0' : '-1'
        };
        if(btnEl === 'a'){
            cfg.href = '#';
        }
        this.lnk = el.createChild(cfg);
        
        
        if(!this.disabled) {
            this.enableLnkListeners();
        }else {
            this.disableAllListeners();
        }
        
        this.on({
            disable: this.disableAllListeners,
            enable: this.enableAllListeners,
            scope: this
        });

        this.setupKeyMap();
    },
    setupKeyMap : function(){
        this.keyMap = new Ext.KeyMap(this.lnk, [
            {
                key: [
                    Ext.EventObject.BACKSPACE, 
                    Ext.EventObject.DELETE, 
                    Ext.EventObject.SPACE
                ],
                fn: this.preDestroy,
                scope: this
            }, {
                key: [
                    Ext.EventObject.RIGHT,
                    Ext.EventObject.DOWN
                ],
                fn: function(){
                    this.moveFocus('right');
                },
                scope: this
            },
            {
                key: [Ext.EventObject.LEFT,Ext.EventObject.UP],
                fn: function(){
                    this.moveFocus('left');
                },
                scope: this
            },
            {
                key: [Ext.EventObject.HOME],
                fn: function(){
                    var l = this.owner.items.get(0).el.focus();
                    if(l){
                        l.el.focus();
                    }
                },
                scope: this
            },
            {
                key: [Ext.EventObject.END],
                fn: function(){
                    this.owner.el.focus();
                },
                scope: this
            },
            {
                key: Ext.EventObject.ENTER,
                fn: function(){
                }
            }
        ]);
        this.keyMap.stopEvent = true;
    },
    moveFocus : function(dir) {
        var el = this.el[dir == 'left' ? 'prev' : 'next']() || this.owner.el;
	
        el.focus.defer(100,el);
    },

    preDestroy : function(supressEffect) {
    	if(this.fireEvent('remove', this) === false){
	    	return;
	    }	
    	var actionDestroy = function(){
            if(this.owner.navigateItemsWithTab){
                this.moveFocus('right');
            }
            this.hidden.remove();
            this.hidden = null;
            this.destroy();
        };
        
        if(supressEffect){
            actionDestroy.call(this);
        } else {
            this.el.hide({
                duration: 0.2,
                callback: actionDestroy,
                scope: this
            });
        }
        return this;
    },
    kill : function(){
    	this.hidden.remove();
        this.hidden = null;
        this.purgeListeners();
        this.destroy();
    },
    onDisable : function() {
    	if(this.hidden){
    	    this.hidden.dom.setAttribute('disabled', 'disabled');
    	}
    	this.keyMap.disable();
    	Ext.ux.form.SuperBoxSelectItem.superclass.onDisable.call(this);
    },
    onEnable : function() {
    	if(this.hidden){
    	    this.hidden.dom.removeAttribute('disabled');
    	}
    	this.keyMap.enable();
    	Ext.ux.form.SuperBoxSelectItem.superclass.onEnable.call(this);
    },
    onDestroy : function() {
        Ext.destroy(
            this.lnk,
            this.el
        );
        
        Ext.ux.form.SuperBoxSelectItem.superclass.onDestroy.call(this);
    }
});
/*
 * ux.ManagedIFrame for ExtJS Library 3.1+
 * Copyright(c) 2008-2009 Active Group, Inc.
 * licensing@theactivegroup.com
 * http://licensing.theactivegroup.com
 */
Ext.namespace("Ext.ux.plugin");Ext.onReady(function(){var a=Ext.util.CSS;if(a){a.getRule(".x-hide-nosize")||a.createStyleSheet(".x-hide-nosize{height:0px!important;width:0px!important;border:none!important;zoom:1;}.x-hide-nosize * {height:0px!important;width:0px!important;border:none!important;zoom:1;}");a.refreshCache()}});(function(){var g=Ext.Element,b=Ext.lib.Anim,a=g.prototype;var f="visibility",d="display",c="hidden",i="none";var e={};e.El={setDisplayed:function(k){var j=this;j.visibilityCls?(j[k!==false?"removeClass":"addClass"](j.visibilityCls)):a.setDisplayed.call(j,k);return j},isDisplayed:function(){return !(this.hasClass(this.visibilityCls)||this.isStyle(d,i))},fixDisplay:function(){var j=this;a.fixDisplay.call(j);j.visibilityCls&&j.removeClass(j.visibilityCls)},isVisible:function(k){var l=this.visible||(!this.isStyle(f,c)&&(this.visibilityCls?!this.hasClass(this.visibilityCls):!this.isStyle(d,i)));if(k!==true||!l){return l}var m=this.dom.parentNode,j=/^body/i;while(m&&!j.test(m.tagName)){if(!Ext.fly(m,"_isVisible").isVisible()){return false}m=m.parentNode}return true},isStyle:a.isStyle||function(j,k){return this.getStyle(j)==k}};Ext.override(g.Flyweight,e.El);Ext.ux.plugin.VisibilityMode=function(k){Ext.apply(this,k||{});var j=Ext.util.CSS;if(j&&!Ext.isIE&&this.fixMaximizedWindow!==false&&!Ext.ux.plugin.VisibilityMode.MaxWinFixed){j.updateRule(".x-window-maximized-ct","overflow","");Ext.ux.plugin.VisibilityMode.MaxWinFixed=true}};Ext.extend(Ext.ux.plugin.VisibilityMode,Object,{bubble:true,fixMaximizedWindow:true,elements:null,visibilityCls:"x-hide-nosize",hideMode:"nosize",ptype:"uxvismode",init:function(n){var k=this.hideMode||n.hideMode,m=this,j=Ext.Container.prototype.bubble,l=function(){var q=[this.collapseEl,this.actionMode].concat(m.elements||[]);Ext.each(q,function(r){m.extend(this[r]||r)},this);var p={visFixed:true,animCollapse:false,animFloat:false,hideMode:k,defaults:this.defaults||{}};p.defaults.hideMode=k;Ext.apply(this,p);Ext.apply(this.initialConfig||{},p)};n.on("render",function(){if(m.bubble!==false&&this.ownerCt){j.call(this.ownerCt,function(){this.visFixed||this.on("afterlayout",l,this,{single:true})})}l.call(this)},n,{single:true})},extend:function(j,k){j&&Ext.each([].concat(j),function(l){if(l&&l.dom){if("visibilityCls" in l){return}Ext.apply(l,e.El);l.visibilityCls=k||this.visibilityCls}},this);return this}});Ext.preg&&Ext.preg("uxvismode",Ext.ux.plugin.VisibilityMode);Ext.provide&&Ext.provide("uxvismode")})();(function(){var J=Ext.Element,n,u=Ext.lib.Dom,al=Ext.lib.Anim,m=Ext.EventManager,aj=Ext.lib.Event,an=document,t=function(){},ap=Object.prototype,aA=ap.toString,B=/^body/i,q="[object HTMLDocument]";if(!Ext.elCache||parseInt(Ext.version.replace(/\./g,""),10)<311){alert("Ext Release "+Ext.version+" is not supported")}Ext._documents={};Ext._documents[Ext.id(document,"_doc")]=Ext.elCache;var T=u.resolveDocumentCache=function(E,aF){Ext._documents[Ext.id(document,"_doc")]=Ext.elCache;var aE=e(E),aG=Ext.isDocument(aE)?Ext.id(aE):aF,A=Ext._documents[aG]||null;return A||(aG?Ext._documents[aG]={}:null)},aD=u.clearDocumentCache=function(A){delete Ext._documents[A]};J.addMethods||(J.addMethods=function(A){Ext.apply(J.prototype,A||{})});Ext.removeNode=function(aH){var aG=aH?aH.dom||aH:null,aE,aF,A=T(aG),E;if(aG&&(aF=A[aG.id])&&(aE=aF.el)){if(aE.dom){Ext.enableNestedListenerRemoval?m.purgeElement(aE.dom,true):m.removeAll(aE.dom)}delete A[aG.id];delete aE.dom;delete aE._context;aE=null}if(aG&&!aG.navigator&&!Ext.isDocument(aG)&&!B.test(aG.tagName)){(E=aG.parentElement||aG.parentNode)&&E.removeChild(aG)}aG=E=null};var f=function(aI,aG){var aH=typeof aI==="function"?aI:function aF(){};var aE=aH._ovl;if(!aE){aE={base:aH};aE[aH.length||0]=aH;aH=function aF(){var aL=arguments.callee._ovl;var aK=aL[arguments.length]||aL.base;return aK&&aK!=arguments.callee?aK.apply(this,arguments):undefined}}var aJ=[].concat(aG);for(var E=0,A=aJ.length;E<A;++E){aE[aJ[E].length]=aJ[E]}aH._ovl=aE;var aF=null;return aH};Ext.applyIf(Ext,{overload:f(f,[function(A){return f(null,A)},function(aE,E,A){return aE[E]=f(aE[E],A)}]),isArray:function(A){return !!A&&aA.apply(A)=="[object Array]"},isObject:function(A){return !!A&&typeof A=="object"},isDocument:function(E,A){var aG=E?E.dom||E:null;var aF=aG&&((aA.apply(aG)==q)||(aG&&aG.nodeType==9));if(aF&&A){try{aF=!!aG.location}catch(aE){return false}}return aF},isWindow:function(A){var E=A?A.dom||A:null;return E?!!E.navigator||aA.apply(E)=="[object Window]":false},isIterable:function(A){if(Ext.isArray(A)||A.callee){return true}if(/NodeList|HTMLCollection/.test(aA.call(A))){return true}return((typeof A.nextNode!="undefined"||A.item)&&Ext.isNumber(A.length))},isElement:function(A){return A&&Ext.type(A)=="element"},isEvent:function(A){return aA.apply(A)=="[object Event]"||(Ext.isObject(A)&&!Ext.type(o.constructor)&&(window.event&&A.clientX&&A.clientX==window.event.clientX))},isFunction:function(A){return !!A&&typeof A=="function"},isEventSupported:function(aF,aG){var aE={select:"input",change:"input",submit:"form",reset:"form",load:"img",error:"img",abort:"img"},A={},aH=/^on/i,E=function(aK,aJ){var aI=Ext.getDom(aJ);return(aI?(Ext.isElement(aI)||Ext.isDocument(aI)?aI.nodeName.toLowerCase():aJ.self?"#window":aJ||"#object"):aJ||"div")+":"+aK};return function(aM,aO){aM=(aM||"").replace(aH,"");var aN,aL=false;var aJ="on"+aM;var aI=(aO?aO:aE[aM])||"div";var aK=E(aM,aI);if(aK in A){return A[aK]}aN=Ext.isString(aI)?an.createElement(aI):aO;aL=(!!aN&&(aJ in aN));aL||(aL=window.Event&&!!(String(aM).toUpperCase() in window.Event));if(!aL&&aN){aN.setAttribute&&aN.setAttribute(aJ,"return;");aL=Ext.isFunction(aN[aJ])}A[aK]=aL;aN=null;return aL}}()});var ao=function(A){return J;return J[(A.tagName||"-").toUpperCase()]||J};var H;function ai(A,E){if(!H){H=new Ext.Element.Flyweight()}H.dom=Ext.getDom(A,null,E);return H}Ext.apply(Ext,{get:J.get=function(aF,aK){if(!aF){return null}var aJ=Ext.isDocument(aF);Ext.isDocument(aK)||(aK=an);var aI,aH,E,A=T(aK);if(typeof aF=="string"){aH=Ext.getDom(aF,null,aK);if(!aH){return null}if(A[aF]&&A[aF].el){aI=A[aF].el;aI.dom=aH}else{aI=J.addToCache(new (ao(aH))(aH,null,aK))}return aI}else{if(aJ){if(!Ext.isDocument(aF,true)){return false}A=T(aF);if(A[Ext.id(aF)]&&A[aF.id].el){return A[aF.id].el}var aG=function(){};aG.prototype=J.prototype;var aE=new aG();aE.dom=aF;aE.id=Ext.id(aF,"_doc");aE._isDoc=true;J.addToCache(aE,null,A);return aE}else{if(aF instanceof J){if(aF.dom){aF.id=Ext.id(aF.dom)}else{aF.dom=aF.id?Ext.getDom(aF.id,true):null}if(aF.dom){A=T(aF);(A[aF.id]||(A[aF.id]={data:{},events:{}})).el=aF}return aF}else{if(aF.tagName||Ext.isWindow(aF)){A=T(aF);E=Ext.id(aF);if(A[E]&&(aI=A[E].el)){aI.dom=aF}else{aI=J.addToCache(new (ao(aF))(aF,null,aK),null,A)}return aI}else{if(aF.isComposite){return aF}else{if(Ext.isArray(aF)){return Ext.get(aK,aK).select(aF)}}}}}}return null},getDom:function(E,A,aG){var aF=aG||an;if(!E||!aF){return null}if(E.dom){return E.dom}else{if(Ext.isString(E)){var aE=aF.getElementById(E);if(aE&&Ext.isIE&&A){if(E==aE.getAttribute("id")){return aE}else{return null}}return aE}else{return E}}},getBody:function(E){var A=u.getDocument(E)||an;return Ext.get(A.body||A.documentElement)},getDoc:Ext.overload([Ext.getDoc,function(A){return Ext.get(A,A)}])});J.data=function(E,A,aE){E=J.get(E);if(!E){return null}var aF=T(E)[E.id].data;if(arguments.length==2){return aF[A]}else{return(aF[A]=aE)}};J.addToCache=function(E,aG,A){aG=aG||Ext.id(E);var aF=A||T(E);aF[aG]={el:E.dom?E:Ext.get(E),data:{},events:{}};var aE=aF[aG].el.dom;(aE.getElementById||aE.navigator)&&(aF[aG].skipGC=true);return aF[aG].el};J.removeFromCache=function(E,A){if(E&&E.id){var aE=A||T(E);delete aE[E.id]}};J.OFFSETS=3;J.ASCLASS=4;J.visibilityCls="x-hide-nosize";var ar={},L=/(-[a-z])/gi,G=function(A,E){return E.charAt(1).toUpperCase()},i=/alpha\(opacity=(.*)\)/i,l=/^\s+|\s+$/g,ay=/marginRight/,C=Ext.isIE?"styleFloat":"cssFloat",at=an.defaultView,ac="visibilityMode",av="asclass",M="originalDisplay",ad="padding",Q="margin",aw="border",c="-left",j="-right",p="-top",au="-bottom",P="-width",az=Math,Y="opacity",X="visibility",K="display",ag="offsets",z="nosize",av="asclass",ae="hidden",Z="none",W="isVisible",v="isClipped",d="overflow",S="overflow-x",R="overflow-y",D="originalClip",O="x-masked",F="x-masked-relative",ah={l:aw+c+P,r:aw+j+P,t:aw+p+P,b:aw+au+P},am={l:ad+c,r:ad+j,t:ad+p,b:ad+au},r={l:Q+c,r:Q+j,t:Q+p,b:Q+au},I=J.data,aB=Ext.getDom,s=Ext.get,af=Ext.DomHelper,aa=/^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,aC=Ext.util.CSS,ak=function(E){var A=I(E,M);if(A===undefined){I(E,M,A="")}return A},k=function(E){var A=I(E,ac);if(A===undefined){I(E,ac,A=J.prototype.visibilityMode)}return A};function aq(A){return ar[A]||(ar[A]=A=="float"?C:A.replace(L,G))}J.addMethods({getDocument:function(){return this._context||(this._context=e(this))},remove:function(E,A){var aE=this.dom;if(aE){Ext.removeNode(aE);delete this._context;delete this.dom}},appendChild:function(A,E){return s(A,E||this.getDocument()).appendTo(this)},appendTo:function(A,E){aB(A,false,E||this.getDocument()).appendChild(this.dom);return this},insertBefore:function(A,E){(A=aB(A,false,E||this.getDocument())).parentNode.insertBefore(this.dom,A);return this},insertAfter:function(A,E){(A=aB(A,false,E||this.getDocument())).parentNode.insertBefore(this.dom,A.nextSibling);return this},insertFirst:function(E,A){E=E||{};if(E.nodeType||E.dom||typeof E=="string"){E=aB(E);this.dom.insertBefore(E,this.dom.firstChild);return !A?s(E):E}else{return this.createChild(E,this.dom.firstChild,A)}},replace:function(A,E){A=s(A,E||this.getDocument());this.insertBefore(A);A.remove();return this},replaceWith:function(A,aE){var E=this;if(A.nodeType||A.dom||typeof A=="string"){A=aB(A,false,aE||E.getDocument());E.dom.parentNode.insertBefore(A,E.dom)}else{A=af.insertBefore(E.dom,A)}var aF=T(E);Ext.removeNode(E.dom);E.id=Ext.id(E.dom=A);J.addToCache(E.isFlyweight?new (ao(E.dom))(E.dom,null,aF):E);return E},insertHtml:function(E,aE,A){var aF=af.insertHtml(E,this.dom,aE);return A?Ext.get(aF,e(aF)):aF},isVisible:function(A){var E=this,aG=E.dom,aE=aG.parentNode,aF=I(aG,W);if(typeof aF!="boolean"){aF=!E.hasClass(E.visibilityCls||J.visibilityCls)&&!E.isStyle(X,ae)&&!E.isStyle(K,Z);I(aG,W,aF)}if(A!==true||!aF){return aF}while(aE&&!B.test(aE.tagName)){if(!Ext.fly(aE,"_isVisible").isVisible()){return false}aE=aE.parentNode}return true},setVisible:function(aG,A){var aE=this,aF=aE.dom,E=k(aF);if(typeof A=="string"){switch(A){case K:E=J.DISPLAY;break;case X:E=J.VISIBILITY;break;case ag:E=J.OFFSETS;break;case z:case av:E=J.ASCLASS;break}aE.setVisibilityMode(E);A=false}if(!A||!aE.anim){if(E==J.ASCLASS){aE[aG?"removeClass":"addClass"](aE.visibilityCls||J.visibilityCls)}else{if(E==J.DISPLAY){return aE.setDisplayed(aG)}else{if(E==J.OFFSETS){if(!aG){aE.hideModeStyles={position:aE.getStyle("position"),top:aE.getStyle("top"),left:aE.getStyle("left")};aE.applyStyles({position:"absolute",top:"-10000px",left:"-10000px"})}else{aE.applyStyles(aE.hideModeStyles||{position:"",top:"",left:""});delete aE.hideModeStyles}}else{aE.fixDisplay();aF.style.visibility=aG?"visible":ae}}}}else{if(aG){aE.setOpacity(0.01);aE.setVisible(true)}aE.anim({opacity:{to:(aG?1:0)}},aE.preanim(arguments,1),null,0.35,"easeIn",function(){aG||aE.setVisible(false).setOpacity(1)})}I(aF,W,aG);return aE},hasMetrics:function(){var A=this;return A.isVisible()||(k(A.dom)==J.VISIBILITY)},setDisplayed:function(E){var aE=this.dom,A=k(aE);if(typeof E=="boolean"){if(A==J.ASCLASS){return this.setVisible(E)}I(this.dom,W,E);E=E?ak(aE):Z}this.setStyle(K,E);return this},enableDisplayMode:function(A){this.setVisibilityMode(J.DISPLAY);if(!Ext.isEmpty(A)){I(this.dom,M,A)}return this},scrollIntoView:function(aE,aH){var aM=this.getDocument(),aN=Ext.getDom(aE,null,aM)||Ext.getBody(aM).dom,aG=this.dom,aF=this.getOffsetsTo(aN),aJ=aF[0]+aN.scrollLeft,aQ=aF[1]+aN.scrollTop,aO=aQ+aG.offsetHeight,E=aJ+aG.offsetWidth,A=aN.clientHeight,aK=parseInt(aN.scrollTop,10),aP=parseInt(aN.scrollLeft,10),aI=aK+A,aL=aP+aN.clientWidth;if(aG.offsetHeight>A||aQ<aK){aN.scrollTop=aQ}else{if(aO>aI){aN.scrollTop=aO-A}}aN.scrollTop=aN.scrollTop;if(aH!==false){if(aG.offsetWidth>aN.clientWidth||aJ<aP){aN.scrollLeft=aJ}else{if(E>aL){aN.scrollLeft=E-aN.clientWidth}}aN.scrollLeft=aN.scrollLeft}return this},contains:function(A){try{return !A?false:u.isAncestor(this.dom,A.dom?A.dom:A)}catch(E){return false}},getScroll:function(){var aI=this.dom,aH=this.getDocument(),A=aH.body,aE=aH.documentElement,E,aG,aF;if(Ext.isDocument(aI)||aI==A){if(Ext.isIE&&u.docIsStrict(aH)){E=aE.scrollLeft;aG=aE.scrollTop}else{E=window.pageXOffset;aG=window.pageYOffset}aF={left:E||(A?A.scrollLeft:0),top:aG||(A?A.scrollTop:0)}}else{aF={left:aI.scrollLeft,top:aI.scrollTop}}return aF},getStyle:function(){var A=at&&at.getComputedStyle?function E(aL){var aI=!this._isDoc?this.dom:null,aE,aH,aF,aJ,aK=Ext.isWebKit,aJ,aG;if(!aI||!aI.style){return null}aG=aI.style;aL=aq(aL);aH=at.getComputedStyle(aI,null);aF=(aH)?aH[aL]:null;if(aK){if(aF&&ay.test(aL)&&aG.position!="absolute"&&aF!="0px"){aJ=aG.display;aG.display="inline-block";aF=at.getComputedStyle(aI,null)[aL];aG.display=aJ}else{if(aF=="rgba(0, 0, 0, 0)"){aF="transparent"}}}return aF||aG[aL]}:function E(aJ){var aH=!this._isDoc?this.dom:null,aE,aG,aF;if(!aH||!aH.style){return null}aF=aH.style;if(aJ==Y){if(aF.filter.match){if(aE=aF.filter.match(i)){var aI=parseFloat(aE[1]);if(!isNaN(aI)){return aI?aI/100:0}}}return 1}aJ=aq(aJ);return((aG=aH.currentStyle)?aG[aJ]:null)||aH.style[aJ]};var E=null;return A}(),setStyle:function(aF,aE){if(this._isDoc||Ext.isDocument(this.dom)){return this}var A,E;if(typeof aF!="object"){A={};A[aF]=aE;aF=A}for(E in aF){if(aF.hasOwnProperty(E)){aE=aF[E];E==Y?this.setOpacity(aE):this.dom.style[aq(E)]=aE}}return this},center:function(A){return this.alignTo(A||this.getDocument(),"c-c")},mask:function(E,aH){var aJ=this,aF=aJ.dom,aI=Ext.DomHelper,aG="ext-el-mask-msg",A,aK;if(aJ.getStyle("position")=="static"){aJ.addClass(F)}if((A=I(aF,"maskMsg"))){A.remove()}if((A=I(aF,"mask"))){A.remove()}aK=aI.append(aF,{cls:"ext-el-mask"},true);I(aF,"mask",aK);aJ.addClass(O);aK.setDisplayed(true);if(typeof E=="string"){var aE=aI.append(aF,{cls:aG,cn:{tag:"div"}},true);I(aF,"maskMsg",aE);aE.dom.className=aH?aG+" "+aH:aG;aE.dom.firstChild.innerHTML=E;aE.setDisplayed(true);aE.center(aJ)}if(Ext.isIE&&!(Ext.isIE7&&Ext.isStrict)&&aJ.getStyle("height")=="auto"){aK.setSize(undefined,aJ.getHeight())}return aK},unmask:function(){var aE=this,aF=aE.dom,A=I(aF,"mask"),E=I(aF,"maskMsg");if(A){if(E){E.remove();I(aF,"maskMsg",undefined)}A.remove();I(aF,"mask",undefined)}aE.removeClass([O,F])},isMasked:function(){var A=I(this.dom,"mask");return A&&A.isVisible()},getCenterXY:function(){return this.getAlignToXY(this.getDocument(),"c-c")},getAnchorXY:function(aG,aL,aQ){aG=(aG||"tl").toLowerCase();aQ=aQ||{};var aK=this,aN=this.getDocument(),E=aK.dom==aN.body||aK.dom==aN,aO=aQ.width||E?u.getViewWidth(false,aN):aK.getWidth(),aI=aQ.height||E?u.getViewHeight(false,aN):aK.getHeight(),aP,A=Math.round,aE=aK.getXY(),aM=aK.getScroll(),aJ=E?aM.left:!aL?aE[0]:0,aH=E?aM.top:!aL?aE[1]:0,aF={c:[A(aO*0.5),A(aI*0.5)],t:[A(aO*0.5),0],l:[0,A(aI*0.5)],r:[aO,A(aI*0.5)],b:[A(aO*0.5),aI],tl:[0,0],bl:[0,aI],br:[aO,aI],tr:[aO,0]};aP=aF[aG];return[aP[0]+aJ,aP[1]+aH]},anchorTo:function(E,aH,aE,A,aJ,aK){var aI=this,aG=aI.dom;function aF(){ai(aG).alignTo(E,aH,aE,A);Ext.callback(aK,ai(aG))}Ext.EventManager.onWindowResize(aF,aI);if(!Ext.isEmpty(aJ)){Ext.EventManager.on(window,"scroll",aF,aI,{buffer:!isNaN(aJ)?aJ:50})}aF.call(aI);return aI},getScroll:function(){var aI=this.dom,aH=this.getDocument(),A=aH.body,aE=aH.documentElement,E,aG,aF;if(aI==aH||aI==A){if(Ext.isIE&&u.docIsStrict(aH)){E=aE.scrollLeft;aG=aE.scrollTop}else{E=window.pageXOffset;aG=window.pageYOffset}aF={left:E||(A?A.scrollLeft:0),top:aG||(A?A.scrollTop:0)}}else{aF={left:aI.scrollLeft,top:aI.scrollTop}}return aF},getAlignToXY:function(aF,aR,aS){var a6;aF=Ext.get(aF,a6=this.getDocument());if(!aF||!aF.dom){throw"Element.getAlignToXY with an element that doesn't exist"}aS=aS||[0,0];aR=(aR=="?"?"tl-bl?":(!/-/.test(aR)&&aR!=""?"tl-"+aR:aR||"tl-bl")).toLowerCase();var a3=this,aY=a3.dom,a5,a4,aK,aJ,aM,aW,aP,aN=u.getViewWidth(false,a6)-10,aX=u.getViewHeight(false,a6)-10,E,aG,aH,aI,aO,aQ,a0=a6.documentElement,aL=a6.body,aV=(a0.scrollLeft||aL.scrollLeft||0)+5,aU=(a0.scrollTop||aL.scrollTop||0)+5,aZ=false,aE="",A="",aT=aR.match(/^([a-z]+)-([a-z]+)(\?)?$/);if(!aT){throw"Element.getAlignToXY with an invalid alignment "+aR}aE=aT[1];A=aT[2];aZ=!!aT[3];a5=a3.getAnchorXY(aE,true);a4=aF.getAnchorXY(A,false);aK=a4[0]-a5[0]+aS[0];aJ=a4[1]-a5[1]+aS[1];if(aZ){aM=a3.getWidth();aW=a3.getHeight();aP=aF.getRegion();E=aE.charAt(0);aG=aE.charAt(aE.length-1);aH=A.charAt(0);aI=A.charAt(A.length-1);aO=((E=="t"&&aH=="b")||(E=="b"&&aH=="t"));aQ=((aG=="r"&&aI=="l")||(aG=="l"&&aI=="r"));if(aK+aM>aN+aV){aK=aQ?aP.left-aM:aN+aV-aM}if(aK<aV){aK=aQ?aP.right:aV}if(aJ+aW>aX+aU){aJ=aO?aP.top-aW:aX+aU-aW}if(aJ<aU){aJ=aO?aP.bottom:aU}}return[aK,aJ]},adjustForConstraints:function(aE,A,E){return this.getConstrainToXY(A||this.getDocument(),false,E,aE)||aE},getConstrainToXY:function(E,A,aE,aG){var aF={top:0,left:0,bottom:0,right:0};return function(aI,aQ,aJ,aH){var aS=this.getDocument();aI=Ext.get(aI,aS);aJ=aJ?Ext.applyIf(aJ,aF):aF;var aR,aK,aP=0,aN=0;if(aI.dom==aS.body||aI.dom==aS){aR=u.getViewWidth(false,aS);aK=u.getViewHeight(false,aS)}else{aR=aI.dom.clientWidth;aK=aI.dom.clientHeight;if(!aQ){var aL=aI.getXY();aP=aL[0];aN=aL[1]}}var aV=aI.getScroll();aP+=aJ.left+aV.left;aN+=aJ.top+aV.top;aR-=aJ.right;aK-=aJ.bottom;var aU=aP+aR,aM=aN+aK,aT=aH||(!aQ?this.getXY():[this.getLeft(true),this.getTop(true)]);x=aT[0],y=aT[1],offset=this.getConstrainOffset(),w=this.dom.offsetWidth+offset,h=this.dom.offsetHeight+offset;var aO=false;if((x+w)>aU){x=aU-w;aO=true}if((y+h)>aM){y=aM-h;aO=true}if(x<aP){x=aP;aO=true}if(y<aN){y=aN;aO=true}return aO?[x,y]:false}}(),getConstrainOffset:function(){return 0},getCenterXY:function(){return this.getAlignToXY(Ext.getBody(this.getDocument()),"c-c")},center:function(A){return this.alignTo(A||Ext.getBody(this.getDocument()),"c-c")},findParent:function(aJ,aI,E){var aG=this.dom,aF=this.getDocument(),A=aF.body,aH=0,aE;if(Ext.isGecko&&aA.call(aG)=="[object XULElement]"){return null}aI=aI||50;if(isNaN(aI)){aE=Ext.getDom(aI,null,aF);aI=Number.MAX_VALUE}while(aG&&aG.nodeType==1&&aH<aI&&aG!=A&&aG!=aE){if(Ext.DomQuery.is(aG,aJ)){return E?Ext.get(aG,aF):aG}aH++;aG=aG.parentNode}return null},clip:function(){var A=this,E=A.dom;if(!I(E,v)){I(E,v,true);I(E,D,{o:A.getStyle(d),x:A.getStyle(S),y:A.getStyle(R)});A.setStyle(d,ae);A.setStyle(S,ae);A.setStyle(R,ae)}return A},unclip:function(){var A=this,aE=A.dom;if(I(aE,v)){I(aE,v,false);var E=I(aE,D);if(E.o){A.setStyle(d,E.o)}if(E.x){A.setStyle(S,E.x)}if(E.y){A.setStyle(R,E.y)}}return A},getViewSize:function(){var aE=this.getDocument(),aF=this.dom,A=(aF==aE||aF==aE.body);if(A){var E=Ext.lib.Dom;return{width:E.getViewWidth(),height:E.getViewHeight()}}else{return{width:aF.clientWidth,height:aF.clientHeight}}},getStyleSize:function(){var aG=this,A,aF,aI=this.getDocument(),aJ=this.dom,E=(aJ==aI||aJ==aI.body),aE=aJ.style;if(E){var aH=Ext.lib.Dom;return{width:aH.getViewWidth(),height:aH.getViewHeight()}}if(aE.width&&aE.width!="auto"){A=parseFloat(aE.width);if(aG.isBorderBox()){A-=aG.getFrameWidth("lr")}}if(aE.height&&aE.height!="auto"){aF=parseFloat(aE.height);if(aG.isBorderBox()){aF-=aG.getFrameWidth("tb")}}return{width:A||aG.getWidth(true),height:aF||aG.getHeight(true)}}});Ext.apply(u,{getDocument:function(aE,aF){var aG=null;try{aG=Ext.getDom(aE,null,null)}catch(E){}var A=Ext.isDocument(aG);if(A){if(aF){return Ext.isDocument(aG,aF)?aG:null}return aG}return aG?aG.ownerDocument||aG.document:null},docIsStrict:function(A){return(Ext.isDocument(A)?A:this.getDocument(A)).compatMode=="CSS1Compat"},getViewWidth:Ext.overload([u.getViewWidth||function(A){},function(){return this.getViewWidth(false)},function(A,E){return A?this.getDocumentWidth(E):this.getViewportWidth(E)}]),getViewHeight:Ext.overload([u.getViewHeight||function(A){},function(){return this.getViewHeight(false)},function(A,E){return A?this.getDocumentHeight(E):this.getViewportHeight(E)}]),getDocumentHeight:Ext.overload([u.getDocumentHeight||t,function(A){if(A=this.getDocument(A)){return Math.max(!this.docIsStrict(A)?A.body.scrollHeight:A.documentElement.scrollHeight,this.getViewportHeight(A))}return undefined}]),getDocumentWidth:Ext.overload([u.getDocumentWidth||t,function(A){if(A=this.getDocument(A)){return Math.max(!this.docIsStrict(A)?A.body.scrollWidth:A.documentElement.scrollWidth,this.getViewportWidth(A))}return undefined}]),getViewportHeight:Ext.overload([u.getViewportHeight||t,function(A){if(A=this.getDocument(A)){if(Ext.isIE){return this.docIsStrict(A)?A.documentElement.clientHeight:A.body.clientHeight}else{return A.defaultView.innerHeight}}return undefined}]),getViewportWidth:Ext.overload([u.getViewportWidth||t,function(A){if(A=this.getDocument(A)){return !this.docIsStrict(A)&&!Ext.isOpera?A.body.clientWidth:Ext.isIE?A.documentElement.clientWidth:A.defaultView.innerWidth}return undefined}]),getXY:Ext.overload([u.getXY||t,function(A,aF){if(typeof A=="string"){A=Ext.getDom(A,null,aF);var aE=this.getDocument(A),E=aE?(aE.body||aE.documentElement):null;if(!A||!E||A==E){return[0,0]}}return this.getXY(A)}])});var e=u.getDocument,N=J._flyweights;Ext.fly=J.fly=function(aE,A,aF){var E=null;A=A||"_global";if(aE=Ext.getDom(aE,null,aF)){(E=N[A]=(N[A]||new J.Flyweight())).dom=aE;Ext.isDocument(aE)&&(E._isDoc=true)}return E};var ax=function(){};ax.prototype=J.prototype;J.Flyweight=function(A){this.dom=A};J.Flyweight.prototype=new ax();J.Flyweight.prototype.isFlyweight=true;function b(aF,aH,aK,aG,aE,aM){aF=Ext.getDom(aF);if(!aF){return}var E=Ext.id(aF),A=T(aF);A[E]||J.addToCache(aF,E,A);var aL=A[E].events||{},aI;aI=aj.on(aF,aH,aE);aL[aH]=aL[aH]||[];aL[aH].push([aK,aE,aM,aI,aG]);if(aF.addEventListener&&aH=="mousewheel"){var aJ=["DOMMouseScroll",aE,false];aF.addEventListener.apply(aF,aJ);Ext.EventManager.addListener(window,"beforeunload",function(){aF.removeEventListener.apply(aF,aJ)})}if(aH=="mousedown"&&an==aF){Ext.EventManager.stoppedMouseDownEvent.addListener(aE)}}function g(A,E){return function(){var aE=Ext.toArray(arguments);if(E.target==Ext.EventObject.setEvent(aE[0]).target){A.apply(this,aE)}}}function ab(E,aE,A){return function(aF){A.delay(aE.buffer,E,null,[new Ext.EventObjectImpl(aF)])}}function V(aG,aF,A,aE,E){return function(aH){Ext.EventManager.removeListener(aF,A,aE,E);aG(aH)}}function a(E,aE,A){return function(aG){var aF=new Ext.util.DelayedTask(E);(A.tasks||(A.tasks=[])).push(aF);aF.delay(aE.delay||10,E,null,[new Ext.EventObjectImpl(aG)])}}function U(aH,aG,A,aJ,aK){var E=!Ext.isObject(A)?{}:A,aE=Ext.getDom(aH),aF;aJ=aJ||E.fn;aK=aK||E.scope;if(!aE){throw'Error listening for "'+aG+'". Element "'+aH+"\" doesn't exist."}function aI(aM){if(!window.Ext){return}aM=Ext.EventObject.setEvent(aM);var aL;if(E.delegate){if(!(aL=aM.getTarget(E.delegate,aE))){return}}else{aL=aM.target}if(E.stopEvent){aM.stopEvent()}if(E.preventDefault){aM.preventDefault()}if(E.stopPropagation){aM.stopPropagation()}if(E.normalized){aM=aM.browserEvent}aJ.call(aK||aE,aM,aL,E)}if(E.target){aI=g(aI,E)}if(E.delay){aI=a(aI,E,aJ)}if(E.single){aI=V(aI,aE,aG,aJ,aK)}if(E.buffer){aF=new Ext.util.DelayedTask(aI);aI=ab(aI,E,aF)}b(aE,aG,aJ,aF,aI,aK);return aI}Ext.apply(m,{addListener:m.on=function(aE,A,aG,aF,E){if(typeof A=="object"){var aJ=A,aH,aI;for(aH in aJ){if(!aJ.hasOwnProperty(aH)){continue}aI=aJ[aH];if(!aa.test(aH)){if(Ext.isFunction(aI)){U(aE,aH,aJ,aI,aJ.scope)}else{U(aE,aH,aI)}}}}else{U(aE,A,E,aG,aF)}},removeListener:m.un=function(aH,aI,aM,aP){var E=Ext.getDom(aH);E&&Ext.get(E);var aN=E?T(E):{},aK=E&&((aN[E.id]||{events:{}}).events)[aI]||[],A,aG,aE,aF,aJ,aL,aO;for(aG=0,aJ=aK.length;aG<aJ;aG++){if(Ext.isArray(aL=aK[aG])&&aL[0]==aM&&(!aP||aL[2]==aP)){aL[4]&&aL[4].cancel();aF=aM.tasks&&aM.tasks.length;if(aF){while(aF--){aM.tasks[aF].cancel()}delete aM.tasks}A=aL[1];aj.un(E,aI,aj.extAdapter?aL[3]:A);if(A&&aI=="mousewheel"&&E.addEventListener){E.removeEventListener("DOMMouseScroll",A,false)}if(A&&aI=="mousedown"&&an==E){Ext.EventManager.stoppedMouseDownEvent.removeListener(A)}aK.splice(aG,1);if(aK.length===0){delete aN[E.id].events[aI]}aO=aN[E.id].events;for(aF in aO){if(aO.hasOwnProperty(aF)){return false}}aN[E.id].events={};return false}}},removeAll:function(aE){if(!(aE=Ext.getDom(aE))){return}var E=aE.id,aM=T(aE)||{},aN=aM[E]||{},aL=aN.events||{},aI,aH,aJ,aF,aK,aG,A;for(aF in aL){if(aL.hasOwnProperty(aF)){aI=aL[aF];for(aH=0,aJ=aI.length;aH<aJ;aH++){aK=aI[aH];aK[4]&&aK[4].cancel();if(aK[0]&&aK[0].tasks&&(aG=aK[0].tasks.length)){while(aG--){aK[0].tasks[aG].cancel()}delete aK.tasks}A=aK[1];aj.un(aE,aF,aj.extAdapter?aK[3]:A);if(A&&aE.addEventListener&&aF=="mousewheel"){aE.removeEventListener("DOMMouseScroll",A,false)}if(A&&(an==aE)&&aF=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.removeListener(A)}}}}aM[E]&&(aM[E].events={})},getListeners:function(aE,E){aE=Ext.getDom(aE);if(!aE){return}var aG=(Ext.get(aE)||{}).id,A=T(aE),aF=(A[aG]||{}).events||{};return aF[E]||null},purgeElement:function(aE,A,aG){aE=Ext.getDom(aE);var E=Ext.id(aE),aJ=T(aE),aK=(aJ[E]||{}).events||{},aF,aI,aH;if(aG){if(aK.hasOwnProperty(aG)){aI=aK[aG];for(aF=0,aH=aI.length;aF<aH;aF++){m.removeListener(aE,aG,aI[aF][0])}}}else{m.removeAll(aE)}if(A&&aE&&aE.childNodes){for(aF=0,aH=aE.childNodes.length;aF<aH;aF++){m.purgeElement(aE.childNodes[aF],A,aG)}}}});aj.getListeners=function(E,A){return Ext.EventManager.getListeners(E,A)};Ext.provide&&Ext.provide("multidom")})();(function(){var El=Ext.Element,ElFrame,ELD=Ext.lib.Dom,EMPTYFN=function(){},OP=Object.prototype,addListener=function(){var handler;if(window.addEventListener){handler=function F(el,eventName,fn,capture){el.addEventListener(eventName,fn,!!capture)}}else{if(window.attachEvent){handler=function F(el,eventName,fn,capture){el.attachEvent("on"+eventName,fn)}}else{handler=function F(){}}}var F=null;return handler}(),removeListener=function(){var handler;if(window.removeEventListener){handler=function F(el,eventName,fn,capture){el.removeEventListener(eventName,fn,(capture))}}else{if(window.detachEvent){handler=function F(el,eventName,fn){el.detachEvent("on"+eventName,fn)}}else{handler=function F(){}}}var F=null;return handler}();if(typeof ELD.getDocument!="function"){alert("MIF 2.1.4 requires multidom support")}if(!Ext.elCache||parseInt(Ext.version.replace(/\./g,""),10)<311){alert("Ext Release "+Ext.version+" is not supported")}Ext.ns("Ext.ux.ManagedIFrame","Ext.ux.plugin");var MIM,MIF=Ext.ux.ManagedIFrame,MIFC;var frameEvents=["documentloaded","domready","focus","blur","resize","scroll","unload","scroll","exception","message","reset"];var reSynthEvents=new RegExp("^("+frameEvents.join("|")+")","i");Ext.ux.ManagedIFrame.Element=Ext.extend(Ext.Element,{constructor:function(element,forceNew,doc){var d=doc||document,elCache=ELD.resolveDocumentCache(d),dom=Ext.getDom(element,false,d);if(!dom||!(/^(iframe|frame)/i).test(dom.tagName)){return null}var id=Ext.id(dom);this.dom=dom;this.id=id;(elCache[id]||(elCache[id]={el:this,events:{},data:{}})).el=this;this.dom.name||(this.dom.name=this.id);if(Ext.isIE){document.frames&&(document.frames[this.dom.name]||(document.frames[this.dom.name]=this.dom))}this.dom.ownerCt=this;MIM.register(this);if(!this._observable){(this._observable=new Ext.util.Observable()).addEvents("documentloaded","domready","exception","resize","message","blur","focus","unload","scroll","reset");this._observable.addEvents("_docready","_docload")}this.on(Ext.isIE?"readystatechange":"load",this.loadHandler,this,Ext.isOpera?{buffer:this.operaLoadBuffer||2000}:null);this.on("error",this.loadHandler,this)},destructor:function(){MIM.deRegister(this);this.removeAllListeners();Ext.destroy(this.frameShim,this.DDM);this.hideMask(true);delete this.loadMask;this.reset();this.manager=null;this.dom.ownerCt=null},cleanse:function(forceReclean,deep){if(this.isCleansed&&forceReclean!==true){return this}var d=this.dom,n=d.firstChild,nx;while(d&&n){nx=n.nextSibling;deep&&Ext.fly(n).cleanse(forceReclean,deep);Ext.removeNode(n);n=nx}this.isCleansed=true;return this},src:null,CSS:null,manager:null,operaLoadBuffer:2000,disableMessaging:true,domReadyRetries:7500,focusOnLoad:Ext.isIE,eventsFollowFrameLinks:true,remove:function(){this.destructor.apply(this,arguments);ElFrame.superclass.remove.apply(this,arguments)},getDocument:function(){return this.dom?this.dom.ownerDocument:document},submitAsTarget:function(submitCfg){var opt=submitCfg||{},D=this.getDocument(),form=Ext.getDom(opt.form?opt.form.form||opt.form:null,false,D)||Ext.DomHelper.append(D.body,{tag:"form",cls:"x-hidden x-mif-form",encoding:"multipart/form-data"}),formFly=Ext.fly(form,"_dynaForm"),formState={target:form.target||"",method:form.method||"",encoding:form.encoding||"",enctype:form.enctype||"",action:form.action||""},encoding=opt.encoding||form.encoding,method=opt.method||form.method||"POST";formFly.set({target:this.dom.name,method:method,encoding:encoding,action:opt.url||opt.action||form.action});if(method=="POST"||!!opt.enctype){formFly.set({enctype:opt.enctype||form.enctype||encoding})}var hiddens,hd,ps;if(opt.params&&(ps=Ext.isFunction(opt.params)?opt.params():opt.params)){hiddens=[];Ext.iterate(ps=typeof ps=="string"?Ext.urlDecode(ps,false):ps,function(n,v){Ext.fly(hd=D.createElement("input")).set({type:"hidden",name:n,value:v});form.appendChild(hd);hiddens.push(hd)})}opt.callback&&this._observable.addListener("_docready",opt.callback,opt.scope,{single:true});this._frameAction=true;this._targetURI=location.href;this.showMask();(function(){form.submit();hiddens&&Ext.each(hiddens,Ext.removeNode,Ext);if(formFly.hasClass("x-mif-form")){formFly.remove()}else{formFly.set(formState)}delete El._flyweights._dynaForm;formFly=null;this.hideMask(true)}).defer(100,this);return this},resetUrl:(function(){return Ext.isIE&&Ext.isSecure?Ext.SSL_SECURE_URL:"about:blank"})(),setSrc:function(url,discardUrl,callback,scope){var src=url||this.src||this.resetUrl;var O=this._observable;this._unHook();Ext.isFunction(callback)&&O.addListener("_docload",callback,scope||this,{single:true});this.showMask();(discardUrl!==true)&&(this.src=src);var s=this._targetURI=(Ext.isFunction(src)?src()||"":src);try{this._frameAction=true;this.dom.src=s;this.checkDOM()}catch(ex){O.fireEvent.call(O,"exception",this,ex)}return this},setLocation:function(url,discardUrl,callback,scope){var src=url||this.src||this.resetUrl;var O=this._observable;this._unHook();Ext.isFunction(callback)&&O.addListener("_docload",callback,scope||this,{single:true});this.showMask();var s=this._targetURI=(Ext.isFunction(src)?src()||"":src);if(discardUrl!==true){this.src=src}try{this._frameAction=true;this.getWindow().location.replace(s);this.checkDOM()}catch(ex){O.fireEvent.call(O,"exception",this,ex)}return this},reset:function(src,callback,scope){this._unHook();var loadMaskOff=false,s=src,win=this.getWindow(),O=this._observable;if(this.loadMask){loadMaskOff=this.loadMask.disabled;this.loadMask.disabled=false}this.hideMask(true);if(win){this.isReset=true;var cb=callback;O.addListener("_docload",function(frame){if(this.loadMask){this.loadMask.disabled=loadMaskOff}Ext.isFunction(cb)&&(cb=cb.apply(scope||this,arguments));O.fireEvent("reset",this)},this,{single:true});Ext.isFunction(s)&&(s=src());s=this._targetURI=Ext.isEmpty(s,true)?this.resetUrl:s;win.location?(win.location.href=s):O.fireEvent("_docload",this)}return this},scriptRE:/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/gi,update:function(content,loadScripts,callback,scope){loadScripts=loadScripts||this.getUpdater().loadScripts||false;content=Ext.DomHelper.markup(content||"");content=loadScripts===true?content:content.replace(this.scriptRE,"");var doc;if((doc=this.getFrameDocument())&&!!content.length){this._unHook();this.src=null;this.showMask();Ext.isFunction(callback)&&this._observable.addListener("_docload",callback,scope||this,{single:true});this._targetURI=location.href;doc.open();this._frameAction=true;doc.write(content);doc.close();this.checkDOM()}else{this.hideMask(true);Ext.isFunction(callback)&&callback.call(scope,this)}return this},execCommand:function(command,userInterface,value,validate){var doc,assert;if((doc=this.getFrameDocument())&&!!command){try{Ext.isIE&&this.getWindow().focus();assert=validate&&Ext.isFunction(doc.queryCommandEnabled)?doc.queryCommandEnabled(command):true;return assert&&doc.execCommand(command,!!userInterface,value)}catch(eex){return false}}return false},setDesignMode:function(active){var doc;(doc=this.getFrameDocument())&&(doc.designMode=(/on|true/i).test(String(active))?"on":"off")},getUpdater:function(){return this.updateManager||(this.updateManager=new MIF.Updater(this))},getHistory:function(){var h=null;try{h=this.getWindow().history}catch(eh){}return h},get:function(el){var doc=this.getFrameDocument();return doc?Ext.get(el,doc):doc=null},fly:function(el,named){var doc=this.getFrameDocument();return doc?Ext.fly(el,named,doc):null},getDom:function(el){var d;if(!el||!(d=this.getFrameDocument())){return(d=null)}return Ext.getDom(el,d)},select:function(selector,unique){var d;return(d=this.getFrameDocument())?Ext.Element.select(selector,unique,d):d=null},query:function(selector){var d;return(d=this.getFrameDocument())?Ext.DomQuery.select(selector,d):null},removeNode:Ext.removeNode,_renderHook:function(){this._windowContext=null;this.CSS=this.CSS?this.CSS.destroy():null;this._hooked=false;try{if(this.writeScript('(function(){(window.hostMIF = parent.document.getElementById("'+this.id+'").ownerCt)._windowContext='+(Ext.isIE?"window":'{eval:function(s){return new Function("return ("+s+")")();}}')+";})()")){var w,p=this._frameProxy,D=this.getFrameDocument();if(w=this.getWindow()){p||(p=this._frameProxy=this._eventProxy.createDelegate(this));addListener(w,"focus",p);addListener(w,"blur",p);addListener(w,"resize",p);addListener(w,"unload",p);D&&addListener(Ext.isIE?w:D,"scroll",p)}D&&(this.CSS=new Ext.ux.ManagedIFrame.CSS(D))}}catch(ex){}return this.domWritable()},_unHook:function(){if(this._hooked){this._windowContext&&(this._windowContext.hostMIF=null);this._windowContext=null;var w,p=this._frameProxy;if(p&&this.domWritable()&&(w=this.getWindow())){removeListener(w,"focus",p);removeListener(w,"blur",p);removeListener(w,"resize",p);removeListener(w,"unload",p);removeListener(Ext.isIE?w:this.getFrameDocument(),"scroll",p)}}ELD.clearDocumentCache&&ELD.clearDocumentCache(this.id);this.CSS=this.CSS?this.CSS.destroy():null;this.domFired=this._frameAction=this.domReady=this._hooked=false},_windowContext:null,getFrameDocument:function(){var win=this.getWindow(),doc=null;try{doc=(Ext.isIE&&win?win.document:null)||this.dom.contentDocument||window.frames[this.dom.name].document||null}catch(gdEx){ELD.clearDocumentCache&&ELD.clearDocumentCache(this.id);return false}doc=(doc&&Ext.isFunction(ELD.getDocument))?ELD.getDocument(doc,true):doc;return doc},getDoc:function(){var D=this.getFrameDocument();return Ext.get(D,D)},getBody:function(){var d;return(d=this.getFrameDocument())?this.get(d.body||d.documentElement):null},getDocumentURI:function(){var URI,d;try{URI=this.src&&(d=this.getFrameDocument())?d.location.href:null}catch(ex){}return URI||(Ext.isFunction(this.src)?this.src():this.src)},getWindowURI:function(){var URI,w;try{URI=(w=this.getWindow())?w.location.href:null}catch(ex){}return URI||(Ext.isFunction(this.src)?this.src():this.src)},getWindow:function(){var dom=this.dom,win=null;try{win=dom.contentWindow||window.frames[dom.name]||null}catch(gwEx){}return win},scrollChildIntoView:function(child,container,hscroll){this.fly(child,"_scrollChildIntoView").scrollIntoView(this.getDom(container)||this.getBody().dom,hscroll);return this},print:function(){try{var win;if(win=this.getWindow()){Ext.isIE&&win.focus();win.print()}}catch(ex){throw new MIF.Error("printexception",ex.description||ex.message||ex)}return this},domWritable:function(){return !!Ext.isDocument(this.getFrameDocument(),true)&&!!this._windowContext},execScript:function(block,useDOM){try{if(this.domWritable()){if(useDOM){this.writeScript(block)}else{return this._windowContext.eval(block)}}else{throw new MIF.Error("execscript-secure-context")}}catch(ex){this._observable.fireEvent.call(this._observable,"exception",this,ex);return false}return true},writeScript:function(block,attributes){attributes=Ext.apply({},attributes||{},{type:"text/javascript",text:block});try{var head,script,doc=this.getFrameDocument();if(doc&&typeof doc.getElementsByTagName!="undefined"){if(!(head=doc.getElementsByTagName("head")[0])){head=doc.createElement("head");doc.getElementsByTagName("html")[0].appendChild(head)}if(head&&(script=doc.createElement("script"))){for(var attrib in attributes){if(attributes.hasOwnProperty(attrib)&&attrib in script){script[attrib]=attributes[attrib]}}return !!head.appendChild(script)}}}catch(ex){this._observable.fireEvent.call(this._observable,"exception",this,ex)}finally{script=head=null}return false},loadFunction:function(fn,useDOM,invokeIt){var name=fn.name||fn;var fnSrc=fn.fn||window[fn];name&&fnSrc&&this.execScript(name+"="+fnSrc,useDOM);invokeIt&&this.execScript(name+"()")},loadHandler:function(e,target){var rstatus=(this.dom||{}).readyState||(e||{}).type;if(this.eventsFollowFrameLinks||this._frameAction||this.isReset){switch(rstatus){case"domready":case"DOMFrameContentLoaded":case"domfail":this._onDocReady(rstatus);break;case"load":case"complete":var frame=this;this._frameAction&&setTimeout(function(){frame._onDocLoaded(rstatus)},0.01);this._frameAction=false;break;case"error":this._observable.fireEvent.apply(this._observable,["exception",this].concat(arguments));break;default:}this.frameState=rstatus}},_onDocReady:function(eventName){var w,obv=this._observable,D;try{if(!this.isReset&&this.focusOnLoad&&(w=this.getWindow())){w.focus()}(D=this.getDoc())&&(D.isReady=true)}catch(ex){}obv.fireEvent("_docready",this);if(!this.domFired&&(this._hooked=this._renderHook())){this.domFired=true;this.isReset||obv.fireEvent.call(obv,"domready",this)}this.domReady=true;this.hideMask()},_onDocLoaded:function(eventName){var obv=this._observable,w;this.domReady||this._onDocReady("domready");obv.fireEvent("_docload",this);this.isReset||obv.fireEvent("documentloaded",this);this.hideMask(true);this._frameAction=this.isReset=false},checkDOM:function(win){if(Ext.isGecko){return}var n=0,frame=this,domReady=false,b,l,d,max=this.domReadyRetries||2500,polling=false,startLocation=(this.getFrameDocument()||{location:{}}).location.href;(function(){d=frame.getFrameDocument()||{location:{}};polling=(d.location.href!==startLocation||d.location.href===frame._targetURI);if(frame.domReady){return}domReady=polling&&((b=frame.getBody())&&!!(b.dom.innerHTML||"").length)||false;if(d.location.href&&!domReady&&(++n<max)){setTimeout(arguments.callee,2);return}frame.loadHandler({type:domReady?"domready":"domfail"})})()},filterEventOptionsRe:/^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,addListener:function(eventName,fn,scope,options){if(typeof eventName=="object"){var o=eventName;for(var e in o){if(this.filterEventOptionsRe.test(e)){continue}if(typeof o[e]=="function"){this.addListener(e,o[e],o.scope,o)}else{this.addListener(e,o[e].fn,o[e].scope,o[e])}}return}if(reSynthEvents.test(eventName)){var O=this._observable;if(O){O.events[eventName]||(O.addEvents(eventName));O.addListener.call(O,eventName,fn,scope||this,options)}}else{ElFrame.superclass.addListener.call(this,eventName,fn,scope||this,options)}return this},removeListener:function(eventName,fn,scope){var O=this._observable;if(reSynthEvents.test(eventName)){O&&O.removeListener.call(O,eventName,fn,scope||this,options)}else{ElFrame.superclass.removeListener.call(this,eventName,fn,scope||this)}return this},removeAllListeners:function(){Ext.EventManager.removeAll(this.dom);var O=this._observable;O&&O.purgeListeners.call(this._observable);return this},showMask:function(msg,msgCls,maskCls){var lmask=this.loadMask;if(lmask&&!lmask.disabled){this.mask(msg||lmask.msg,msgCls||lmask.msgCls,maskCls||lmask.maskCls,lmask.maskEl)}},hideMask:function(forced){var tlm=this.loadMask||{};if(forced||(tlm.hideOnReady&&this.domReady)){this.unmask()}},mask:function(msg,msgCls,maskCls,maskEl){this._mask&&this.unmask();var p=Ext.get(maskEl)||this.parent(".ux-mif-mask-target")||this.parent();if(p.getStyle("position")=="static"&&!p.select("iframe,frame,object,embed").elements.length){p.addClass("x-masked-relative")}p.addClass("x-masked");this._mask=Ext.DomHelper.append(p,{cls:maskCls||"ux-mif-el-mask"},true);this._mask.setDisplayed(true);this._mask._agent=p;if(typeof msg=="string"){this._maskMsg=Ext.DomHelper.append(p,{cls:msgCls||"ux-mif-el-mask-msg",style:{visibility:"hidden"},cn:{tag:"div",html:msg}},true);this._maskMsg.setVisibilityMode(Ext.Element.VISIBILITY).center(p).setVisible(true)}if(Ext.isIE&&!(Ext.isIE7&&Ext.isStrict)&&this.getStyle("height")=="auto"){this._mask.setSize(undefined,this._mask.getHeight())}return this._mask},unmask:function(){var a;if(this._mask){(a=this._mask._agent)&&a.removeClass(["x-masked-relative","x-masked"]);if(this._maskMsg){this._maskMsg.remove();delete this._maskMsg}this._mask.remove();delete this._mask}},createFrameShim:function(imgUrl,shimCls){this.shimCls=shimCls||this.shimCls||"ux-mif-shim";this.frameShim||(this.frameShim=this.next("."+this.shimCls)||Ext.DomHelper.append(this.dom.parentNode,{tag:"img",src:imgUrl||Ext.BLANK_IMAGE_URL,cls:this.shimCls,galleryimg:"no"},true));this.frameShim&&(this.frameShim.autoBoxAdjust=false);return this.frameShim},toggleShim:function(show){var shim=this.frameShim||this.createFrameShim();var cls=this.shimCls+"-on";!show&&shim.removeClass(cls);show&&!shim.hasClass(cls)&&shim.addClass(cls)},load:function(loadCfg){var um;if(um=this.getUpdater()){if(loadCfg&&loadCfg.renderer){um.setRenderer(loadCfg.renderer);delete loadCfg.renderer}um.update.apply(um,arguments)}return this},_eventProxy:function(e){if(!e){return}e=Ext.EventObject.setEvent(e);var be=e.browserEvent||e,er,args=[e.type,this];if(!be.eventPhase||(be.eventPhase==(be.AT_TARGET||2))){if(e.type=="resize"){var doc=this.getFrameDocument();doc&&(args.push({height:ELD.getDocumentHeight(doc),width:ELD.getDocumentWidth(doc)},{height:ELD.getViewportHeight(doc),width:ELD.getViewportWidth(doc)},{height:ELD.getViewHeight(false,doc),width:ELD.getViewWidth(false,doc)}))}er=this._observable?this._observable.fireEvent.apply(this._observable,args.concat(Array.prototype.slice.call(arguments,0))):null;(e.type=="unload")&&this._unHook()}return er},sendMessage:function(message,tag,origin){},postMessage:function(message,origin){}});ElFrame=Ext.Element.IFRAME=Ext.Element.FRAME=Ext.ux.ManagedIFrame.Element;var fp=ElFrame.prototype;Ext.override(ElFrame,{on:fp.addListener,un:fp.removeListener,getUpdateManager:fp.getUpdater});Ext.ux.ManagedIFrame.ComponentAdapter=function(){};Ext.ux.ManagedIFrame.ComponentAdapter.prototype={version:2.14,defaultSrc:null,unsupportedText:"Inline frames are NOT enabled/supported by your browser.",hideMode:!Ext.isIE&&!!Ext.ux.plugin.VisibilityMode?"nosize":"display",animCollapse:Ext.isIE,animFloat:Ext.isIE,disableMessaging:true,eventsFollowFrameLinks:true,frameConfig:null,focusOnLoad:Ext.isIE,frameEl:null,useShim:false,autoScroll:true,autoLoad:null,getId:function(){return this.id||(this.id="mif-comp-"+(++Ext.Component.AUTO_ID))},stateEvents:["documentloaded"],stateful:false,setAutoScroll:function(auto){var scroll=Ext.value(auto,this.autoScroll===true);this.rendered&&this.getFrame()&&this.frameEl.setOverflow((this.autoScroll=scroll)?"auto":"hidden");return this},getContentTarget:function(){return this.getFrame()},getFrame:function(){if(this.rendered){if(this.frameEl){return this.frameEl}var f=this.items&&this.items.first?this.items.first():null;f&&(this.frameEl=f.frameEl);return this.frameEl}return null},getFrameWindow:function(){return this.getFrame()?this.frameEl.getWindow():null},getFrameDocument:function(){return this.getFrame()?this.frameEl.getFrameDocument():null},getFrameDoc:function(){return this.getFrame()?this.frameEl.getDoc():null},getFrameBody:function(){return this.getFrame()?this.frameEl.getBody():null},resetFrame:function(){this.getFrame()&&this.frameEl.reset.apply(this.frameEl,arguments);return this},submitAsTarget:function(submitCfg){this.getFrame()&&this.frameEl.submitAsTarget.apply(this.frameEl,arguments);return this},load:function(loadCfg){if(loadCfg&&this.getFrame()){var args=arguments;this.resetFrame(null,function(){loadCfg.submitAsTarget?this.submitAsTarget.apply(this,args):this.frameEl.load.apply(this.frameEl,args)},this)}this.autoLoad=loadCfg;return this},doAutoLoad:function(){this.autoLoad&&this.load(typeof this.autoLoad=="object"?this.autoLoad:{url:this.autoLoad})},getUpdater:function(){return this.getFrame()?this.frameEl.getUpdater():null},setSrc:function(url,discardUrl,callback,scope){this.getFrame()&&this.frameEl.setSrc.apply(this.frameEl,arguments);return this},setLocation:function(url,discardUrl,callback,scope){this.getFrame()&&this.frameEl.setLocation.apply(this.frameEl,arguments);return this},getState:function(){var URI=this.getFrame()?this.frameEl.getDocumentURI()||null:null;var state=this.supr().getState.call(this);state=Ext.apply(state||{},{defaultSrc:Ext.isFunction(URI)?URI():URI,autoLoad:this.autoLoad});return state},setMIFEvents:function(){this.addEvents("documentloaded","domready","exception","message","blur","focus","scroll","resize","unload","reset")},sendMessage:function(message,tag,origin){},onAdd:function(C){C.relayTarget&&this.suspendEvents(true)},initRef:function(){if(this.ref){var t=this,levels=this.ref.split("/"),l=levels.length,i;for(i=0;i<l;i++){if(t.ownerCt){t=t.ownerCt}}this.refName=levels[--i];t[this.refName]||(t[this.refName]=this);this.refOwner=t}}};Ext.ux.ManagedIFrame.Component=Ext.extend(Ext.BoxComponent,{ctype:"Ext.ux.ManagedIFrame.Component",initComponent:function(){var C={monitorResize:this.monitorResize||(this.monitorResize=!!this.fitToParent),plugins:(this.plugins||[]).concat(this.hideMode==="nosize"&&Ext.ux.plugin.VisibilityMode?[new Ext.ux.plugin.VisibilityMode({hideMode:"nosize",elements:["bwrap"]})]:[])};MIF.Component.superclass.initComponent.call(Ext.apply(this,Ext.apply(this.initialConfig,C)));this.setMIFEvents()},onRender:function(ct,position){var frCfg=this.frameCfg||this.frameConfig||(this.relayTarget?{name:this.relayTarget.id}:{})||{};var frDOM=frCfg.autoCreate||frCfg;frDOM=Ext.apply({tag:"iframe",id:Ext.id()},frDOM);var el=Ext.getDom(this.el);(el&&el.tagName=="iframe")||(this.autoEl=Ext.apply({name:frDOM.id,frameborder:0},frDOM));MIF.Component.superclass.onRender.apply(this,arguments);if(this.unsupportedText){ct.child("noframes")||ct.createChild({tag:"noframes",html:this.unsupportedText||null})}var frame=this.el;var F;if(F=this.frameEl=(this.el?new MIF.Element(this.el.dom,true):null)){Ext.apply(F,{ownerCt:this.relayTarget||this,disableMessaging:Ext.value(this.disableMessaging,true),focusOnLoad:Ext.value(this.focusOnLoad,Ext.isIE),eventsFollowFrameLinks:Ext.value(this.eventsFollowFrameLinks,true)});F.ownerCt.frameEl=F;F.addClass("ux-mif");if(this.loadMask){var mEl=this.loadMask.maskEl;F.loadMask=Ext.apply({disabled:false,hideOnReady:false,msgCls:"ext-el-mask-msg x-mask-loading",maskCls:"ext-el-mask"},{maskEl:F.ownerCt[String(mEl)]||F.parent("."+String(mEl))||F.parent(".ux-mif-mask-target")||mEl},Ext.isString(this.loadMask)?{msg:this.loadMask}:this.loadMask);Ext.get(F.loadMask.maskEl)&&Ext.get(F.loadMask.maskEl).addClass("ux-mif-mask-target")}F._observable&&(this.relayTarget||this).relayEvents(F._observable,frameEvents.concat(this._msgTagHandlers||[]));delete this.contentEl}},afterRender:function(container){MIF.Component.superclass.afterRender.apply(this,arguments);if(this.fitToParent&&!this.ownerCt){var pos=this.getPosition(),size=(Ext.get(this.fitToParent)||this.getEl().parent()).getViewSize();this.setSize(size.width-pos[0],size.height-pos[1])}this.getEl().setOverflow("hidden");this.setAutoScroll();var F;if(F=this.frameEl){var ownerCt=this.ownerCt;while(ownerCt){ownerCt.on("afterlayout",function(container,layout){Ext.each(["north","south","east","west"],function(region){var reg;if((reg=layout[region])&&reg.split&&reg.split.dd&&!reg._splitTrapped){reg.split.dd.endDrag=reg.split.dd.endDrag.createSequence(MIM.hideShims,MIM);reg.split.on("beforeresize",MIM.showShims,MIM);reg._splitTrapped=MIM._splitTrapped=true}},this)},this,{single:true});ownerCt=ownerCt.ownerCt}if(!!this.ownerCt||this.useShim){this.frameShim=F.createFrameShim()}this.getUpdater().showLoadIndicator=this.showLoadIndicator||false;var resumeEvents=this.relayTarget&&this.ownerCt?this.ownerCt.resumeEvents.createDelegate(this.ownerCt):null;if(this.autoload){this.doAutoLoad()}else{if(this.tpl&&(this.frameData||this.data)){F.update(this.tpl.apply(this.frameData||this.data),true,resumeEvents);delete this.frameData;delete this.data;return}else{if(this.frameMarkup||this.html){F.update(this.frameMarkup||this.html,true,resumeEvents);delete this.html;delete this.frameMarkup;return}else{if(this.defaultSrc){F.setSrc(this.defaultSrc,false)}else{F.reset(null,resumeEvents);return}}}}resumeEvents&&resumeEvents()}},beforeDestroy:function(){var F;if(F=this.getFrame()){F.remove();this.frameEl=this.frameShim=null}this.relayTarget&&(this.relayTarget.frameEl=null);MIF.Component.superclass.beforeDestroy.call(this)}});Ext.override(MIF.Component,MIF.ComponentAdapter.prototype);Ext.reg("mif",MIF.Component);function embed_MIF(config){config||(config={});config.layout="fit";config.items={xtype:"mif",ref:"mifChild",useShim:true,tpl:Ext.value(config.tpl,this.tpl),autoScroll:Ext.value(config.autoScroll,this.autoScroll),defaultSrc:Ext.value(config.defaultSrc,this.defaultSrc),frameMarkup:Ext.value(config.html,this.html),frameData:Ext.value(config.data,this.data),loadMask:Ext.value(config.loadMask,this.loadMask),disableMessaging:Ext.value(config.disableMessaging,this.disableMessaging),eventsFollowFrameLinks:Ext.value(config.eventsFollowFrameLinks,this.eventsFollowFrameLinks),focusOnLoad:Ext.value(config.focusOnLoad,this.focusOnLoad),frameConfig:Ext.value(config.frameConfig||config.frameCfg,this.frameConfig),relayTarget:this};delete config.html;delete config.data;this.setMIFEvents();return config}Ext.ux.ManagedIFrame.Panel=Ext.extend(Ext.Panel,{ctype:"Ext.ux.ManagedIFrame.Panel",bodyCssClass:"ux-mif-mask-target",constructor:function(config){MIF.Panel.superclass.constructor.call(this,embed_MIF.call(this,config))}});Ext.override(MIF.Panel,MIF.ComponentAdapter.prototype);Ext.reg("iframepanel",MIF.Panel);Ext.ux.ManagedIFrame.Portlet=Ext.extend(Ext.ux.ManagedIFrame.Panel,{ctype:"Ext.ux.ManagedIFrame.Portlet",anchor:"100%",frame:true,collapseEl:"bwrap",collapsible:true,draggable:true,cls:"x-portlet"});Ext.reg("iframeportlet",MIF.Portlet);Ext.ux.ManagedIFrame.Window=Ext.extend(Ext.Window,{ctype:"Ext.ux.ManagedIFrame.Window",bodyCssClass:"ux-mif-mask-target",constructor:function(config){MIF.Window.superclass.constructor.call(this,embed_MIF.call(this,config))}});Ext.override(MIF.Window,MIF.ComponentAdapter.prototype);Ext.reg("iframewindow",MIF.Window);Ext.ux.ManagedIFrame.Updater=Ext.extend(Ext.Updater,{showLoading:function(){this.showLoadIndicator&&this.el&&this.el.mask(this.indicatorText)},hideLoading:function(){this.showLoadIndicator&&this.el&&this.el.unmask()},updateComplete:function(response){MIF.Updater.superclass.updateComplete.apply(this,arguments);this.hideLoading()},processFailure:function(response){MIF.Updater.superclass.processFailure.apply(this,arguments);this.hideLoading()}});var styleCamelRe=/(-[a-z])/gi;var styleCamelFn=function(m,a){return a.charAt(1).toUpperCase()};Ext.ux.ManagedIFrame.CSS=function(hostDocument){var doc;if(hostDocument){doc=hostDocument;return{rules:null,destroy:function(){return doc=null},createStyleSheet:function(cssText,id){var ss;if(!doc){return}var head=doc.getElementsByTagName("head")[0];var rules=doc.createElement("style");rules.setAttribute("type","text/css");Ext.isString(id)&&rules.setAttribute("id",id);if(Ext.isIE){head.appendChild(rules);ss=rules.styleSheet;ss.cssText=cssText}else{try{rules.appendChild(doc.createTextNode(cssText))}catch(e){rules.cssText=cssText}head.appendChild(rules);ss=rules.styleSheet?rules.styleSheet:(rules.sheet||doc.styleSheets[doc.styleSheets.length-1])}this.cacheStyleSheet(ss);return ss},removeStyleSheet:function(id){if(!doc||!id){return}var existing=doc.getElementById(id);if(existing){existing.parentNode.removeChild(existing)}},swapStyleSheet:function(id,url){if(!doc){return}this.removeStyleSheet(id);var ss=doc.createElement("link");ss.setAttribute("rel","stylesheet");ss.setAttribute("type","text/css");Ext.isString(id)&&ss.setAttribute("id",id);ss.setAttribute("href",url);doc.getElementsByTagName("head")[0].appendChild(ss)},refreshCache:function(){return this.getRules(true)},cacheStyleSheet:function(ss,media){this.rules||(this.rules={});try{Ext.each(ss.cssRules||ss.rules||[],function(rule){this.hashRule(rule,ss,media)},this);Ext.each(ss.imports||[],function(sheet){sheet&&this.cacheStyleSheet(sheet,this.resolveMedia([sheet,sheet.parentStyleSheet]))},this)}catch(e){}},hashRule:function(rule,sheet,mediaOverride){var mediaSelector=mediaOverride||this.resolveMedia(rule);if(rule.cssRules||rule.rules){this.cacheStyleSheet(rule,this.resolveMedia([rule,rule.parentRule]))}if(rule.styleSheet){this.cacheStyleSheet(rule.styleSheet,this.resolveMedia([rule,rule.ownerRule,rule.parentStyleSheet]))}rule.selectorText&&Ext.each((mediaSelector||"").split(","),function(media){this.rules[((media?media.trim()+":":"")+rule.selectorText).toLowerCase()]=rule},this)},resolveMedia:function(rule){var media;Ext.each([].concat(rule),function(r){if(r&&r.media&&r.media.length){media=r.media;return false}});return media?(Ext.isIE?String(media):media.mediaText):""},getRules:function(refreshCache){if(!this.rules||refreshCache){this.rules={};if(doc){var ds=doc.styleSheets;for(var i=0,len=ds.length;i<len;i++){try{this.cacheStyleSheet(ds[i])}catch(e){}}}}return this.rules},getRule:function(selector,refreshCache,mediaSelector){var rs=this.getRules(refreshCache);if(Ext.type(mediaSelector)=="string"){mediaSelector=mediaSelector.trim()+":"}else{mediaSelector=""}if(!Ext.isArray(selector)){return rs[(mediaSelector+selector).toLowerCase()]}var select;for(var i=0;i<selector.length;i++){select=(mediaSelector+selector[i]).toLowerCase();if(rs[select]){return rs[select]}}return null},updateRule:function(selector,property,value,mediaSelector){Ext.each((mediaSelector||"").split(","),function(mediaSelect){if(!Ext.isArray(selector)){var rule=this.getRule(selector,false,mediaSelect);if(rule){rule.style[property.replace(camelRe,camelFn)]=value;return true}}else{for(var i=0;i<selector.length;i++){if(this.updateRule(selector[i],property,value,mediaSelect)){return true}}}return false},this)}}}};Ext.ux.ManagedIFrame.Manager=function(){var frames={};var implementation={_DOMFrameReadyHandler:function(e){try{var $frame;if($frame=e.target.ownerCt){$frame.loadHandler.call($frame,e)}}catch(rhEx){}},shimCls:"ux-mif-shim",register:function(frame){frame.manager=this;frames[frame.id]=frames[frame.name]={ref:frame};return frame},deRegister:function(frame){delete frames[frame.id];delete frames[frame.name]},hideShims:function(){var mm=MIF.Manager;mm.shimsApplied&&Ext.select("."+mm.shimCls,true).removeClass(mm.shimCls+"-on");mm.shimsApplied=false},showShims:function(){var mm=MIF.Manager;!mm.shimsApplied&&Ext.select("."+mm.shimCls,true).addClass(mm.shimCls+"-on");mm.shimsApplied=true},getFrameById:function(id){return typeof id=="string"?(frames[id]?frames[id].ref||null:null):null},getFrameByName:function(name){return this.getFrameById(name)},getFrameHash:function(frame){return frames[frame.id]||frames[frame.id]||null},destroy:function(){if(document.addEventListener&&!Ext.isOpera){window.removeEventListener("DOMFrameContentLoaded",this._DOMFrameReadyHandler,false)}}};document.addEventListener&&!Ext.isOpera&&window.addEventListener("DOMFrameContentLoaded",implementation._DOMFrameReadyHandler,false);Ext.EventManager.on(window,"beforeunload",implementation.destroy,implementation);return implementation}();MIM=MIF.Manager;MIM.showDragMask=MIM.showShims;MIM.hideDragMask=MIM.hideShims;var winDD=Ext.Window.DD;Ext.override(winDD,{startDrag:winDD.prototype.startDrag.createInterceptor(MIM.showShims),endDrag:winDD.prototype.endDrag.createInterceptor(MIM.hideShims)});Ext.ux.ManagedIFramePanel=MIF.Panel;Ext.ux.ManagedIFramePortlet=MIF.Portlet;Ext.ux.ManagedIframe=function(el,opt){var args=Array.prototype.slice.call(arguments,0),el=Ext.get(args[0]),config=args[0];if(el&&el.dom&&el.dom.tagName=="IFRAME"){config=args[1]||{}}else{config=args[0]||args[1]||{};el=config.autoCreate?Ext.get(Ext.DomHelper.append(config.autoCreate.parent||Ext.getBody(),Ext.apply({tag:"iframe",frameborder:0,cls:"x-mif",src:(Ext.isIE&&Ext.isSecure)?Ext.SSL_SECURE_URL:"about:blank"},config.autoCreate))):null;if(el&&config.unsupportedText){Ext.DomHelper.append(el.dom.parentNode,{tag:"noframes",html:config.unsupportedText})}}var mif=new MIF.Element(el,true);if(mif){Ext.apply(mif,{disableMessaging:Ext.value(config.disableMessaging,true),focusOnLoad:Ext.value(config.focusOnLoad,Ext.isIE),eventsFollowFrameLinks:Ext.value(config.eventsFollowFrameLinks,true),loadMask:!!config.loadMask?Ext.apply({msg:"Loading..",msgCls:"x-mask-loading",maskEl:null,hideOnReady:false,disabled:false},config.loadMask):false,_windowContext:null});config.listeners&&mif.on(config.listeners);if(!!config.html){mif.update(config.html)}else{!!config.src&&mif.setSrc(config.src)}}return mif};Ext.ux.ManagedIFrame.Error=Ext.extend(Ext.Error,{constructor:function(message,arg){this.arg=arg;Ext.Error.call(this,message)},name:"Ext.ux.ManagedIFrame"});Ext.apply(Ext.ux.ManagedIFrame.Error.prototype,{lang:{"documentcontext-remove":"An attempt was made to remove an Element from the wrong document context.","execscript-secure-context":"An attempt was made at script execution within a document context with limited access permissions.",printexception:"An Error was encountered attempting the print the frame contents (document access is likely restricted)."}});Ext.onReady(function(){var CSS=new Ext.ux.ManagedIFrame.CSS(document),rules=[];CSS.getRule(".ux-mif-fill")||(rules.push(".ux-mif-fill{height:100%;width:100%;}"));CSS.getRule(".ux-mif-mask-target")||(rules.push(".ux-mif-mask-target{position:relative;zoom:1;}"));CSS.getRule(".ux-mif-el-mask")||(rules.push(".ux-mif-el-mask {z-index: 100;position: absolute;top:0;left:0;-moz-opacity: 0.5;opacity: .50;*filter: alpha(opacity=50);width: 100%;height: 100%;zoom: 1;} ",".ux-mif-el-mask-msg {z-index: 1;position: absolute;top: 0;left: 0;border:1px solid;background:repeat-x 0 -16px;padding:2px;} ",".ux-mif-el-mask-msg div {padding:5px 10px 5px 10px;border:1px solid;cursor:wait;} "));if(!CSS.getRule(".ux-mif-shim")){rules.push(".ux-mif-shim {z-index:8500;position:absolute;top:0px;left:0px;background:transparent!important;overflow:hidden;display:none;}");rules.push(".ux-mif-shim-on{width:100%;height:100%;display:block;zoom:1;}");rules.push(".ext-ie6 .ux-mif-shim{margin-left:5px;margin-top:3px;}")}if(!CSS.getRule(".x-hide-nosize")){rules.push(".x-hide-nosize{height:0px!important;width:0px!important;visibility:hidden!important;border:none!important;zoom:1;}.x-hide-nosize * {height:0px!important;width:0px!important;visibility:hidden!important;border:none!important;zoom:1;}")}!!rules.length&&CSS.createStyleSheet(rules.join(" "),"mifCSS")});Ext.provide&&Ext.provide("mif")})();
Ext.ns('Ext.ux.grid');

Ext.ux.grid.GridSummary = function(config) {
        Ext.apply(this, config);
};

Ext.extend(Ext.ux.grid.GridSummary, Ext.util.Observable, {
    init : function(grid) {
        this.grid = grid;
        this.cm = grid.getColumnModel();
        this.view = grid.getView();

        var v = this.view;

        // override GridView's onLayout() method
        v.onLayout = this.onLayout;

        v.afterMethod('render', this.refreshSummary, this);
        v.afterMethod('refresh', this.refreshSummary, this);
        v.afterMethod('syncScroll', this.syncSummaryScroll, this);
        v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
        v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
        v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);

        // update summary row on store's add/remove/clear/update events
        grid.store.on({
            add: this.refreshSummary,
            remove: this.refreshSummary,
            clear: this.refreshSummary,
            update: this.refreshSummary,
            scope: this
        });

        if (!this.rowTpl) {
            this.rowTpl = new Ext.Template(
                '<div class="x-grid3-summary-row x-grid3-gridsummary-row-offset">',
                    '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
                        '<tbody><tr>{cells}</tr></tbody>',
                    '</table>',
                '</div>'
            );
            this.rowTpl.disableFormats = true;
        }
        this.rowTpl.compile();

        if (!this.cellTpl) {
            this.cellTpl = new Ext.Template(
                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
                    '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
                "</td>"
            );
            this.cellTpl.disableFormats = true;
        }
        this.cellTpl.compile();
    },

    calculate : function(rs, cm) {
        var data = {}, cfg = cm.config;
        for (var i = 0, len = cfg.length; i < len; i++) { // loop through all columns in ColumnModel
            var cf = cfg[i], // get column's configuration
                cname = cf.dataIndex; // get column dataIndex

            // initialise grid summary row data for
            // the current column being worked on
            data[cname] = 0;

            if (cf.summaryType) {
                for (var j = 0, jlen = rs.length; j < jlen; j++) {
                    var r = rs[j]; // get a single Record
                    data[cname] = Ext.ux.grid.GridSummary.Calculations[cf.summaryType](r.get(cname), r, cname, data, j);
                }
            }
        }

        return data;
    },

    onLayout : function(vw, vh) {
        if (Ext.type(vh) != 'number') { // handles grid's height:'auto' config
            return;
        }
        // note: this method is scoped to the GridView
        if (!this.grid.getGridEl().hasClass('x-grid-hide-gridsummary')) {
            // readjust gridview's height only if grid summary row is visible
            this.scroller.setHeight(vh - this.summary.getHeight());
        }
    },

    syncSummaryScroll : function() {
        var mb = this.view.scroller.dom;

        this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft;
        this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
    },

    doWidth : function(col, w, tw) {
        var s = this.view.summary.dom;

        s.firstChild.style.width = tw;
        s.firstChild.rows[0].childNodes[col].style.width = w;
    },

    doAllWidths : function(ws, tw) {
        var s = this.view.summary.dom, wlen = ws.length;

        s.firstChild.style.width = tw;

        var cells = s.firstChild.rows[0].childNodes;

        for (var j = 0; j < wlen; j++) {
            cells[j].style.width = ws[j];
        }
    },

    doHidden : function(col, hidden, tw) {
        var s = this.view.summary.dom,
            display = hidden ? 'none' : '';

        s.firstChild.style.width = tw;
        s.firstChild.rows[0].childNodes[col].style.display = display;
    },

    renderSummary : function(o, cs, cm) {
        cs = cs || this.view.getColumnData();
        var cfg = cm.config,
            buf = [],
            last = cs.length - 1;

        for (var i = 0, len = cs.length; i < len; i++) {
            var c = cs[i], cf = cfg[i], p = {};

            p.id = c.id;
            p.style = c.style;
            p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');

            if (cf.summaryType || cf.summaryRenderer) {
                p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
            } else {
                p.value = '';
            }
            if (p.value === undefined || p.value === "") {
                p.value = "&#160;";
            }
            buf[buf.length] = this.cellTpl.apply(p);
        }

        return this.rowTpl.apply({
            tstyle: 'width:' + this.view.getTotalWidth() + ';',
            cells: buf.join('')
        });
    },

    refreshSummary : function() {
        var g = this.grid, ds = g.store,
            cs = this.view.getColumnData(),
            cm = this.cm,
            rs = ds.getRange(),
            data = this.calculate(rs, cm),
            buf = this.renderSummary({data: data}, cs, cm);

        if (!this.view.summaryWrap) {
            this.view.summaryWrap = Ext.DomHelper.insertAfter(this.view.scroller, {
                tag: 'div',
                cls: 'x-grid3-gridsummary-row-inner'
            }, true);
        }
        this.view.summary = this.view.summaryWrap.update(buf).first();
    },

    toggleSummary : function(visible) { // true to display summary row
        var el = this.grid.getGridEl();

        if (el) {
            if (visible === undefined) {
                visible = el.hasClass('x-grid-hide-gridsummary');
            }
            el[visible ? 'removeClass' : 'addClass']('x-grid-hide-gridsummary');

            this.view.layout(); // readjust gridview height
        }
    },

    getSummaryNode : function() {
        return this.view.summary;
    }
});
Ext.reg('gridsummary', Ext.ux.grid.GridSummary);

/*
 * all Calculation methods are called on each Record in the Store
 * with the following 5 parameters:
 *
 * v - cell value
 * record - reference to the current Record
 * colName - column name (i.e. the ColumnModel's dataIndex)
 * data - the cumulative data for the current column + summaryType up to the current Record
 * rowIdx - current row index
 */
Ext.ux.grid.GridSummary.Calculations = {
    sum : function(v, record, colName, data, rowIdx) {
        return data[colName] + Ext.num(v, 0);
    },

    count : function(v, record, colName, data, rowIdx) {
        return rowIdx + 1;
    },

    max : function(v, record, colName, data, rowIdx) {
        return Math.max(Ext.num(v, 0), data[colName]);
    },

    min : function(v, record, colName, data, rowIdx) {
        return Math.min(Ext.num(v, 0), data[colName]);
    },

    average : function(v, record, colName, data, rowIdx) {
        var t = data[colName] + Ext.num(v, 0), count = record.store.getCount();
        return rowIdx == count - 1 ? (t / count) : t;
    }
};
Ext.ns('Ext.ux.RapidApp.layout');

/*
  Custom Toolbar layout - currently only extends overflow menu to
  include text items ('tbtext' specifically)
*/
Ext.ux.RapidApp.layout.ToolbarLayout = Ext.extend(Ext.layout.ToolbarLayout, {

  type: 'ra_toolbar',
 
  // WARNING: this is a private method, so the API could change in
  // the future. This is written specifically for ExtJS 3.4.0.
  addComponentToMenu : function(menu, component) {
  
        if (component instanceof Ext.Toolbar.Separator) {
            menu.add('-');

        } else if (Ext.isFunction(component.isXType)) {
            if (component.isXType('splitbutton')) {
                menu.add(this.createMenuConfig(component, true));

            } else if (component.isXType('button')) {
                menu.add(this.createMenuConfig(component, !component.menu));

            } else if (component.isXType('buttongroup')) {
                component.items.each(function(item){
                     this.addComponentToMenu(menu, item);
                }, this);
            }
            
            // -- Extended functionality:
            else if (component.isXType('tbtext')) {
              var cnf = this.createMenuConfig(component, !component.menu);
              cnf.text = component.el.dom.innerHTML;
              menu.add(cnf);
            }
            // --
        }
    }
});

Ext.Container.LAYOUTS.ra_toolbar = Ext.ux.RapidApp.layout.ToolbarLayout;

Ext.ns('Ext.ux.RapidApp.Plugin');

/*
 Ext.ux.RapidApp.Plugin.CmpDataStorePlus
 2011-11-02 by HV

 Plugin for components with stores (such as AppGrid2, AppDV).
 This plugin contains generalized extra functionality applicable
 to any components with stores (note that Ext.data.Store itself
 cannot use plugins because its not a component)
*/
Ext.ux.RapidApp.Plugin.CmpDataStorePlus = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		cmp.datastore_plus_plugin = this;

		delete cmp.store.tiedChildStores;
		
		// bubbles up the parent components and records us as a tied child store:
		cmp.bubbleTieStoreParents = function() {
			this.bubble(function() {
				if(this.bubbleTieStoreParents) {
					if(!this.store.tiedChildStores) {
						this.store.tiedChildStores = {};
						this.store.tiedChildStores[this.store.storeId] = this.store;
					}
					this.store.tiedChildStores[cmp.store.storeId] = cmp.store;
				}
			});
		};
		this.cmp.on('render',cmp.bubbleTieStoreParents,cmp);
		
		
		this.cmp.origInitEvents = this.cmp.initEvents;
		this.cmp.initEvents = function() {
			cmp.origInitEvents.call(cmp);
			if(cmp.loadMask){
				cmp.loadMask = new Ext.LoadMask(cmp.bwrap,
						  Ext.apply({store:cmp.store}, cmp.loadMask));
			}
		}
		
		
		// -- instead of standard store 'autoLoad' param, 'store_autoLoad' happens
		// on 'render' in order to be delayed, in the case of the component being
		// within a collapsed panel, etc. This is partnered with the setting set
		// within DataStore2
		if(typeof cmp.store_autoLoad == 'undefined'){
			// Optionally override value from the cmp, if it exists:
			cmp.store_autoLoad = cmp.store.store_autoLoad;
		}
		if(cmp.store_autoLoad) {
			var onFirstShow;
			onFirstShow = function(){
				// only load if its the first load and not collapsed:
				if(!cmp.store.lastOptions && !cmp.collapsed){
					var params = Ext.isObject(cmp.store_autoLoad) ? cmp.store_autoLoad : {};
					cmp.store.load(params);
				}
			}
			cmp.on('render',onFirstShow,this);
			cmp.on('expand',onFirstShow,this);
		}
		// --
		
		
		if(!cmp.persist_immediately) { cmp.persist_immediately = {}; }
		if(cmp.persist_all_immediately) {
			cmp.persist_immediately = {
				create: true,
				update: true,
				destroy: true
			};
		}
		var miss = false;
		if(cmp.store.api.create && !cmp.persist_immediately.create) { miss = true; }
		if(cmp.store.api.update && !cmp.persist_immediately.update) { miss = true; }
		if(cmp.store.api.destroy && !cmp.persist_immediately.destroy) { miss = true; }
		cmp.persist_all_immediately = true;
		if(miss) { cmp.persist_all_immediately = false; }
		
		Ext.copyTo(this,cmp,[
			'store_buttons',
			'show_store_button_text',
			'store_button_cnf',
			'store_exclude_buttons',
			'close_unsaved_confirm',
			'persist_all_immediately',
			'persist_immediately',
			'store_add_initData',
			'use_edit_form',
			'use_add_form',
			'add_form_url_params',
			'add_form_window_cnf',
			'autoload_added_record',
			'add_records_first',
			'store_exclude_api',
			'store_write_mask',
			'confirm_on_destroy'
		]);
		
		this.exclude_btn_map = {};
		Ext.each(this.store_exclude_buttons,function(item) { this.exclude_btn_map[item] = true; },this);
		if(!this.use_edit_form) { this.exclude_btn_map.edit = true; }
		
		this.initAdditionalStoreMethods.call(this,this.cmp.store,this);
		
		this.cmp.loadedStoreButtons = {};
			
		
		// --vv-- Displays the number of records in the title (superscript)	
		var titleCmp = cmp;
		if(!cmp.title && cmp.ownerCt && (cmp.ownerCt.titleCountLocal || cmp.ownerCt.titleCount )){
			titleCmp = cmp.ownerCt;
		}
		if(titleCmp.title && ( titleCmp.titleCountLocal || titleCmp.titleCount )) {
			cmp.store.on('buttontoggle',function() {
				var count = titleCmp.titleCountLocal ? cmp.store.getCount() : cmp.store.getTotalCount();
				if(count == 0) { count = ''; }
				titleCmp.setTitle(
					titleCmp.initialConfig.title + 
						'&nbsp;<span class="superscript-navy">' + count + '</span>'
				);
			},cmp);
		}
		// --^^--
		
		var plugin = this;
		this.cmp.getStoreButton = function(name,showtext) {
			return plugin.getStoreButton.call(plugin,name,showtext);
		};
		
		if(this.cmp.setup_bbar_store_buttons) {
			this.cmp.on('render',this.insertStoreButtonsBbar,this);
		}
		
		if(this.cmp.setup_tbar_store_buttons) {
			this.cmp.on('render',this.insertStoreButtonsTbar,this);
		}
		
		// Only applies to editor grids; no effect/impact on other components
		// without the beforeedit/afteredit events
		this.cmp.on('beforeedit',this.beforeCellEdit,this);
		this.cmp.on('afteredit',this.cmp.store.saveIfPersist,this);
		
		/**********************/
		/** For Editor Grids **/
		if(Ext.isFunction(cmp.startEditing)){

			cmp.startEditing_orig = cmp.startEditing;
			
			cmp.startEditing = function(row,col) {
				var ed = this.colModel.getCellEditor(col, row);
				if(ed) {
					var field = ed.field;
					if(field && !field.DataStorePlusApplied) {
						
						var stopEditFn = cmp.stopEditing.createDelegate(cmp);
						
						// --- Handle Ctrl+S/Ctrl+Z ('save'/'undo' keyboard shortcuts) for in-progress edit:
						field.on('afterrender', function(){
							if(!field.el) { return; }
							var savebtn = cmp.loadedStoreButtons ? cmp.loadedStoreButtons.save : null;
							new Ext.KeyMap(field.el,{
								ctrl: true,
								key: 's',
								fn: function(k,e){
									e.stopEvent();

									// Complete the edit:
									stopEditFn();

									// If we have a Store save button, also call its handler:
									if(savebtn) { return savebtn.handler.call(this,savebtn); }
								},
								scope: this
							});
							// This is better than the default Ctrl+Z behavior for text fields:
							var xtype = field.getXType();
							if(xtype == 'field' || xtype == 'textfield' || xtype == 'numberfield') {
								new Ext.KeyMap(field.el,{
									ctrl: true,
									key: 'z',
									fn: ed.cancelEdit,
									scope: ed
								});
							}
						},this);
						// ---

						// For combos and other fields with a select listener, automatically
						// finish the edit on select
						field.on('select',stopEditFn);
						
						// For cycle-field/menu-field:
						field.cycleOnShow = false;
						field.manuOnShow = false;
						
						//Call 'expand' for combos and other fields with an expand method (cycle-field)
						if(Ext.isFunction(field.expand)) {
							ed.on('startedit',function(){
								this.expand();
								// If it is specifically a combo, call expand again to make sure
								// it really expands
								if(Ext.isFunction(this.doQuery)) {
									this.expand.defer(50,this);
								}
							},field);
						}
						
						field.DataStorePlusApplied = true;
					}
				}
				return cmp.startEditing_orig.apply(cmp,arguments);
			}
		}
		/**********************/
		/**********************/
		
		
		
		if(Ext.isFunction(this.cmp.getSelectionModel)) {
			// Give Grids getSelectedRecords() so they work like DataViews:
			if(!Ext.isFunction(this.cmp.getSelectedRecords)) {
				this.cmp.getSelectedRecords = function() {
					
					var sm = this.getSelectionModel();
					return sm.getSelections.apply(sm,arguments);
				}
			}
			
			// Give grids the selectionchange event so they work like dataviews:
			var sm = this.cmp.getSelectionModel();
			this.cmp.relayEvents(sm,['selectionchange']);
		}
		
		if(this.close_unsaved_confirm) {
			this.cmp.bubble(function(){
				//this.on('beforedestroy',plugin.beforeDestroy,plugin);
				this.on('beforeremove',plugin.beforeRemoveConfirm,plugin);
			});
		}
		
		// Override 'renderRows' of GridViews to get freshly added rows
		// to show up as dirty. Fields associated with a modified value of
		// undefined don't show up as dirty in the original code. This fixes
		// that:
		this.cmp.on('viewready',function(){
			var view = this.cmp.getView();
			if(!view.renderRowsOrig) { view.renderRowsOrig = view.renderRows; }
			view.renderRows = function(startRow,endRow){
				var grid = view.grid,
				store    = grid.store,
				rowCount = store.getCount(),
				records;
		  
				if (rowCount < 1) { return ''; }
		  
				startRow = startRow || 0;
				endRow   = Ext.isDefined(endRow) ? endRow : rowCount - 1;
				records  = store.getRange(startRow, endRow);
				
				Ext.each(records,function(record) {
					Ext.iterate(record.modified,function(k,v) {
						if(typeof v == 'undefined') { record.modified[k] = null; }
					},this);
				},this);
				return view.renderRowsOrig(startRow,endRow);
			};
		},this);
		
		// Automatically tries to load newly created (from backend) records
		// into loadTarget. This is roughly the same as a double-click on a
		// grid row:
		if(this.autoload_added_record) {
			cmp.store.on('write',function(store,action,result,res,Record){
				if(action == "create" && Ext.isObject(Record) && !Record.phantom && Record.data.loadContentCnf){
					//var loadTarget = Ext.getCmp("main-load-target");
					//return Ext.ux.RapidApp.AppTab.tryLoadTargetRecord(loadTarget,Record,cmp);
          // NEW: consolidated open via improved gridrow_nav (Github Issue #34)
          return Ext.ux.RapidApp.AppTab.gridrow_nav(cmp,Record);
				}
      // New: added tiny delay to prevent race condition (Github Issue #34)
      // Now that we're calling gridrow_nav which now does a REST nav (hashpath)
      // we need to add a delay to prevent a race condition when the add form
      // is in a tab, because it closes after successful create (exactly the same
      // event as this listener) which triggers a hashnav event (by AppTab).
      // Without the delay, the AppTab change of window.location.hash beats the
      // gridrow_nav one, which makes it appear as though it never happened at all
      // (the browser doesn't even see the hash 'change' event). 
      // TODO: I *think* that adding any delay here solves the problem, even if systems
      // are slow/bogged down, but I am not 100% sure...
			},cmp.store,{ delay: 10 });
		}
		
		// -- Display a page-wide mask during save
		if(this.store_write_mask) {
			var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Saving Changes..."});
			var show_mask = function() { myMask.show(); }
			var hide_mask = function() { myMask.hide(); }
			
			cmp.store.on('beforewrite',show_mask,this);
			cmp.store.on('write',hide_mask,this);
			cmp.store.on('exception',hide_mask,this);
		}
		// --
		
		
		// --- Cache the last total count, and supply it back to the server. If the
		// server module supports cache_total_count it will return the cached total back
		// instead of calculating it, increasing performance. When changing pages, sorts, 
		// or other options that don't change the number of rows in the set, there is no
		// reason to calculate the total count over and over
		if(cmp.cache_total_count) {
			// Changes in any request params between requests will clear the cached total 
			// count except for these params:
			var excl_params = [
				'cached_total_count',
				'columns',
				'start',
				'limit',
				'sort',
				'dir',
				'column_summaries'
			];
		
			var get_params_str = function(params) {
				var params = params || {};
				var p = Ext.apply({},params);
				for (i in excl_params) { delete p[excl_params[i]]; }
				
				// Going through this just to make sure we don't get thrown off by the same
				// values but in different orders:
				var keys = [],flat = [];
				for (k in p) { keys.push(k); }
				keys.sort();
				var len = keys.length;
				for (i = 0; i < len; i++) { flat.push(keys[i],p[keys[i]]); }
				return flat.join(',');
			};
			
			// Task to clear the cache:
			cmp.store.clearCachedTotalTask = new Ext.util.DelayedTask(function(){
				if(this.cached_total_count) {
					delete this.cached_total_count;
				}
			},cmp.store);
			
			cmp.store.on('load',function(store) {
				delete store.cached_total_count;
				if(store.reader && store.reader.jsonData) {
					store.cached_total_count = store.reader.jsonData.results;
					store.cached_total_count_params = {};
					Ext.apply(store.cached_total_count_params,store.baseParams);
					Ext.apply(store.cached_total_count_params,store.lastOptions.params);
				}
				// Start a timer to clear the cache after 1 minute of inactivity (loads):
				store.clearCachedTotalTask.delay(60000);
			},this);
			
			
			// Wraping in an afterrender to try to make sure this is the last 'beforeload'
			// handler so we can see any changes made by other components that also hook
			// beforeload, such as MultiFilters. Note: Still seem to have to set all 3 of
			// options.params, store.baseParams, and store.lastOptions.params to be safe...
			cmp.on('afterrender',function(){

				cmp.store.on('beforeload',function(store,options) {
					var next_opts = {};
					Ext.apply(next_opts,store.baseParams || {});
					Ext.apply(next_opts,store.lastOptions.params || {});
					Ext.apply(next_opts,options.params);
					var cur = get_params_str(next_opts);

					if(store.baseParams) {
						delete store.baseParams.cached_total_count;
					}
					
					if(store.lastOptions && store.lastOptions.params) {
						delete store.lastOptions.params.cached_total_count;
					}
					
					if(options && options.params) {
						delete options.params.cached_total_count;
					}
					
					if(store.cached_total_count) {
						store.cached_total_count_params = store.cached_total_count_params || {};
						var prev = get_params_str(store.cached_total_count_params);
						store.cached_total_count_params = next_opts;

						if(prev == cur) {
							options.params.cached_total_count = store.cached_total_count;
							
							if(store.lastOptions && store.lastOptions.params) {
								store.lastOptions.params.cached_total_count = store.cached_total_count;
							}
							
							if(store.baseParams) {
								store.baseParams.cached_total_count = store.cached_total_count;
							}
						}
					}
					return true;
				},this);
			},this);
		}
		// ---
		
	},
	
	store_add_initData: {},
	close_unsaved_confirm: true,
	show_store_button_text: false,
	store_buttons: [ 'add', 'edit', 'delete', 'reload', 'save', 'undo' ],
	store_button_cnf: {},
	store_exclude_buttons: [],
	exclude_btn_map: {},
	use_edit_form: false,
	use_add_form: false,
	add_form_url_params: {},
	add_form_window_cnf: {},
	autoload_added_record: false,
	add_records_first: false,
	store_exclude_api: [],
	store_write_mask: true,
	confirm_on_destroy: true,
		
	initAdditionalStoreMethods: function(store,plugin) {
		
		Ext.each(plugin.store_exclude_api,function(item){
			if(store.api[item]) { delete store.api[item]; }
		});
		
		store.on('beforewrite',function(ds,action,records,options,arg) {
			if(action == 'create'){
				var colnames = [];
				store.fields.each(function(field){ colnames.push(field.name); });
				options.params.create_columns = Ext.encode(colnames);
			}
			
			
			// -- Invalidate the total cache on write operations:
			delete store.cached_total_count;
			if(store.baseParams) {
				delete store.baseParams.cached_total_count;
			}
			if(store.lastOptions && store.lastOptions.params) {
				delete store.lastOptions.params.cached_total_count;
			}
			// --
			
		});
		
		store.addEvents('beforeremove');
		store.removeOrig = store.remove;
		store.remove = function(record) {
			if(store.fireEvent('beforeremove',store,record) !== false) {
				return store.removeOrig.apply(store,arguments);
			}
			return -1;
		};
		
		store.getColumnConfig = function(name) {
			if(!store.columns_map){
				var map = {};
				Ext.each(store.columns,function(cnf){ map[cnf.name] = cnf; },this);
				store.columns_map = map;
			}
			return store.columns_map[name];
		};
    
    store.isEditableColumn = function(name) {
      return store.editable_columns_map[name] ? true : false;
    };
		
		// ----
		// New: track 'loaded_columns' from the server (see metaData in DataStore2)
		store.on('metachange',function(ds,meta){

			if(meta.loaded_columns){
        // New: track individual editable columns:
        store.editable_columns_map = {};
				var loaded_map = {}, edit_count = 0;
				Ext.each(meta.loaded_columns,function(f){
					loaded_map[f] = true; 
					if(store.api.update) {
						var column = store.getColumnConfig(f);
						if(!column){ return; }
						var editable = (column.editor && !column.no_column);
						if(typeof column.allow_edit != 'undefined' && !column.allow_edit) {
							editable = false;
						}
						if(editable || column.allow_edit || column.allow_batchedit) { 
              edit_count++;
              store.editable_columns_map[f] = true;
            }
					}
				},this);
				store.loaded_columns_map = loaded_map;
				// We're tracking the count of loaded and editable fields, which can change from
				// request to request, so we can disable the edit button when that number is 0
				store.editable_fields_count = edit_count;
			}
		},this);
		
		store.hasLoadedColumn = function(name) {
			var map = store.loaded_columns_map || {};
			return map[name];
		};
		store.editableFieldsCount = function() {
			return (store.editable_fields_count || 0);
		};
		// ----
		
		store.getPhantomRecords = function() {
			var records = [];
			store.each(function(Record){
				if(Record.phantom) { records.push(Record); } 
			});
			return records;
		};
		
		store.hasPhantomRecords = function() {
			if(store.getPhantomRecords().length > 0) { return true; }
			return false;
		};
		
		store.addNotAllowed = function() {
			return store.hasPhantomRecords();
		},
		
		store.getNonPhantomModifiedRecords = function() {
			var records = [];
			Ext.each(store.getModifiedRecords(),function(Record){
				if(!Record.phantom) { records.push(Record); } 
			});
			return records;
		};
		
		store.hasPendingChanges = function() {
			if(store.getModifiedRecords().length > 0 || store.removed.length > 0) { 
				return true; 
			}
			return false;
		};
		
		store.getParentStore = function() {
			var parent = plugin.cmp.findParentBy(function(p) {
				if(p.store && p.store.getParentStore) { return true; }
				return false;
			});
			if(parent) { return parent.store; }
			return null;
		};
		
		store.eachTiedChild = function(fn) {
			Ext.iterate(store.tiedChildStores,function(id,str) {
				fn(str);
			});
		};
		
		store.hasAnyPendingChanges = function() {
			var pend = false;
			// If the store has no update or destroy api, it can't have any pending changes
			if(!store.api.update && !store.api.destroy) { return false; }
			store.eachTiedChild(function(s) {
				if(s.hasPendingChanges()) { pend = true; }
			});
			return pend;
		};
		
		store.saveAll = function() {
			store.eachTiedChild(function(s) {
				if(s.hasPendingChanges()) { s.save.call(s); }
			});
		};
		
		store.reloadAll = function() {
			store.eachTiedChild(function(s) { s.reload.call(s); });
		};
		
		store.undoChangesAll = function() {
			store.eachTiedChild(function(s) { 
				if(s.hasPendingChanges()) { s.undoChanges.call(s); }
			});
		};
		
		store.undoChanges = function() {
			var store = this;
			Ext.each(store.getPhantomRecords(),function(Rec){ store.remove(Rec); });
			store.rejectChanges();
			store.fireEvent('buttontoggle',store);
		};
		store.on('beforeload',store.undoChanges,store);
		
		store.getLastRecord = function() {
			var count = store.getCount();
			if(!count) { return null; }
			var index = count - 1;
			return store.getAt(index);
		};
		
		
		
		
		// -- Add Functions -- //
		store.prepareNewRecord = function(initData) {
			return new store.recordType(
				Ext.apply({},initData || plugin.store_add_initData)
			);
		};
		
		store.addRecord = function(initData) {
			var newRec = store.prepareNewRecord(initData);
			var ret;
			if(plugin.add_records_first) {
				ret = store.insert(0,newRec);
			}
			else {
				ret = store.add(newRec);
			}
			if(plugin.persist_immediately.create) { store.saveIfPersist(); }
			return ret;
		};
		
		store.addRecordForm = function(initData) {
			if(plugin.use_add_form == 'tab') {
				return store.addRecordFormTab(initData);
			}
			else {
				return store.addRecordFormWindow(initData);
			}
		};
		
		store.addRecordFormWindow = function(initData) {
			var newRec = store.prepareNewRecord(initData);
			
			var win;
			var close_handler = function(btn) { win.close(); };
			
			plugin.getAddFormPanel(newRec,close_handler,function(formpanel){
			
				var title;
				if(plugin.store_button_cnf.add && plugin.store_button_cnf.add.text) {
					title = plugin.store_button_cnf.add.text;
				}
				else {
					title = 'Add Record'
				}
				if(formpanel.title) { title = formpanel.title; }
				var height = formpanel.height || 500;
				var width = formpanel.width || 700;
				
				delete formpanel.height;
				delete formpanel.width;
				delete formpanel.title;
				
				var win_cfg = Ext.apply({
					title: title,
					layout: 'fit',
					width: width,
					height: height,
					closable: true,
					modal: true,
					items: formpanel
				},plugin.add_form_window_cnf);
				
				if(Ext.isFunction(plugin.cmp.add_form_onPrepare)) {
					plugin.cmp.add_form_onPrepare(win_cfg);
				}
				
				win = new Ext.Window(win_cfg);
				return win.show();
			});
		};
		
		store.addRecordFormTab = function(initData) {
			var loadTarget = Ext.getCmp('main-load-target');
			
			// Fall back to Window if the load target can't be found for a Tab:
			if(!loadTarget) { return store.addRecordFormWindow(initData); }
			
			var newRec = store.prepareNewRecord(initData);
			
			var tab;
			var close_handler = function(btn) { loadTarget.remove(tab); };
			
			plugin.getAddFormPanel(newRec,close_handler,function(formpanel){

				var title, iconCls;
				if(plugin.store_button_cnf.add && plugin.store_button_cnf.add.text) {
					title = plugin.store_button_cnf.add.text;
				}
				else {
					title = 'Add Record'
				}
				
				if(plugin.store_button_cnf.add && plugin.store_button_cnf.add.iconCls) {
					iconCls = plugin.store_button_cnf.add.iconCls;
				}
				
				title = formpanel.title || title;
				iconCls = formpanel.iconCls || iconCls;
				
				delete formpanel.height;
				delete formpanel.width;
				delete formpanel.title;
				delete formpanel.iconCls;
				
				var tab_cfg = {
					title: title,
					iconCls: iconCls,
					layout: 'fit',
					closable: true,
					items: formpanel
				};
				
				if(Ext.isFunction(plugin.cmp.add_form_onPrepare)) {
					plugin.cmp.add_form_onPrepare(tab_cfg);
				}
				
				tab = loadTarget.add(tab_cfg);
				loadTarget.activate(tab);
			});
		};
		// -- -- //
		
		
		
		// -- Edit Functions -- //
		// edit is only allowed if 1 record is selected, or there is only 1 record
		store.editNotAllowed = function() {
			//if(!store.use_edit_form) { return true; }
			var count;
			if(plugin.cmp.getSelectionModel) {
				var sm = plugin.cmp.getSelectionModel();
				count = sm.getCount();
			}
			else {
				count = store.getCount();
			}
			if(!store.editableFieldsCount()){ return true; }
			return (count != 1);
		},
		
		// Gets the record that should be the target of an edit operation. If the
		// component has getSelectedRecords (like a grid or dataview) it is used, 
		// otherwise, the first record of the store is returned
		store.getRecordForEdit = function() {
			if(store.editNotAllowed()) { return null; }
			if(plugin.cmp.getSelectedRecords) {
				var records = plugin.cmp.getSelectedRecords() || [];
				return records[0];
			}
			if(store.getCount() == 1){
				return store.getAt(0);
			}
			return null;
		};
		
		
		store.editRecordForm = function(Rec) {
			Rec = Rec || store.getRecordForEdit();
			if(!Rec) { return; }
			if(plugin.use_edit_form == 'tab') {
				return store.editRecordFormTab(Rec);
			}
			else {
				return store.editRecordFormWindow(Rec);
			}
		};
		
		store.editRecordFormWindow = function(Rec) {
			
			var win;
			var close_handler = function(btn) { win.close(); };
			
			plugin.getEditFormPanel(Rec,close_handler,function(formpanel){
			
				var title;
				if(plugin.store_button_cnf.edit && plugin.store_button_cnf.edit.text) {
					title = plugin.store_button_cnf.edit.text;
				}
				else {
					title = 'Edit Record';
				}
				if(formpanel.title) { title = formpanel.title; }
				var height = formpanel.height || 500;
				var width = formpanel.width || 700;
				
				delete formpanel.height;
				delete formpanel.width;
				delete formpanel.title;
				
				var win_cfg = Ext.apply({
					title: title,
					layout: 'fit',
					width: width,
					height: height,
					closable: true,
					modal: true,
					items: formpanel
				},plugin.add_form_window_cnf); //<-- use same custom config from add
				
				if(Ext.isFunction(plugin.cmp.edit_form_onPrepare)) {
					plugin.cmp.edit_form_onPrepare(win_cfg);
				}
				
				win = new Ext.Window(win_cfg);
				return win.show();
			});
		};
		
		store.editRecordFormTab = function(Rec) {
			var loadTarget = Ext.getCmp('main-load-target');
			
			// Fall back to Window if the load target can't be found for a Tab:
			if(!loadTarget) { return store.editRecordFormWindow(Rec); }
			
			var tab;
			var close_handler = function(btn) { loadTarget.remove(tab); };
			
			plugin.getEditFormPanel(Rec,close_handler,function(formpanel){

				var title, iconCls;
				if(plugin.store_button_cnf.edit && plugin.store_button_cnf.edit.text) {
					title = plugin.store_button_cnf.edit.text;
				}
				else {
					title = 'Edit Record'
				}
				
				if(plugin.store_button_cnf.edit && plugin.store_button_cnf.edit.iconCls) {
					iconCls = plugin.store_button_cnf.edit.iconCls;
				}
				
				title = formpanel.title || title;
				iconCls = formpanel.iconCls || iconCls;
				
				delete formpanel.height;
				delete formpanel.width;
				delete formpanel.title;
				delete formpanel.iconCls;
				
				var tab_cfg = {
					title: title,
					iconCls: iconCls,
					layout: 'fit',
					closable: true,
					items: formpanel
				};
				
				if(Ext.isFunction(plugin.cmp.edit_form_onPrepare)) {
					plugin.cmp.edit_form_onPrepare(tab_cfg);
				}
				
				tab = loadTarget.add(tab_cfg);
				loadTarget.activate(tab);
			});
		};
		// -- -- //
		
		
		
		
		store.removeRecord = function(Record) {
			var ret = store.removeOrig(Record);
			if(plugin.persist_immediately.destroy) { store.saveIfPersist(); }
			return ret;
		};
		
		store.doTransactionIfPersist = function(action) {
			if(!plugin.persist_immediately[action]) { return; }
			return store.doTransactionOrig.apply(store,arguments);
		};
		
		store.saveIfPersist = function() {
			if(!store.doTransactionOrig) {
				store.doTransactionOrig = store.doTransaction;
			}
			store.doTransaction = store.doTransactionIfPersist;
			var ret = store.save.apply(store,arguments);
			store.doTransaction = store.doTransactionOrig;
			return ret;
		};
		
		store.addEvents('buttontoggle');
		store.fireButtonToggleEvent = function(){
			store.fireEvent('buttontoggle',store);
		}
		store.on('load',store.fireButtonToggleEvent,store);
		store.on('read',store.fireButtonToggleEvent,store);
		store.on('write',store.fireButtonToggleEvent,store);
		store.on('datachanged',store.fireButtonToggleEvent,store);
		store.on('clear',store.fireButtonToggleEvent,store);
		store.on('update',store.fireButtonToggleEvent,store);
		store.on('remove',store.fireButtonToggleEvent,store);
		store.on('add',store.fireButtonToggleEvent,store);
		
		// ------
		// NEW: Manually update record.id after an update if the idProperty (typically '___record_pk'
		// in RapidApp) has changed. This is needed to be able to edit the primary column, save it,
		// and then edit the record again. If the record's id isn't updated, the subsequent update
		// will fail because the lookup (DbicLink2) will use the old value, which it won't find anymore
		// This code not only updates the record, but updates its entry in the store (MixedCollection)
		// with the new id/key so that 'getById' and other functions will still operate correctly.
		store.on('write',function(ds,action,result,res,rs){
			if(action != 'update') { return; }
			Ext.each(res.raw.rows,function(row){
				// See update_records in DbicLink2 for where the new key is stored. So this code only
				// fires when working with DbicLink2 on the backend and the pk has changed, otherwise
				// this has no effect
				var idPropertyNew = ds.idProperty + '_new';
				var new_pk = row[idPropertyNew];
				if(!new_pk) { return; }
				
				var ndx = ds.data.indexOfKey(row[ds.idProperty]);
				var record = ds.data.itemAt(ndx);
				if(!record) { return; }
				record.data[ds.idProperty] = new_pk;
				record.id = new_pk;
				
				ds.data.removeAt(ndx);
				ds.data.insert(ndx,record.id,record);
				
			},this);
		},store);
		// ------
		
		
		store.addTrackedToggleFunc = function(func) {
			store.on('buttontoggle',func,store);
		};
		//store.on('buttontoggle',function(){ console.log('buttontoggle'); });
		
		store.buttonConstructor = function(cnf,showtext) {
			if(cnf.text && !cnf.tooltip) {
				cnf.tooltip = cnf.text;
				delete cnf.text;
			}
			
			if (showtext && !cnf.text) {
				cnf.text = cnf.tooltip;
				cnf.tooltip = null;
			}
			
			if (!showtext && cnf.text) {
				delete cnf.text;
			}
      
      // Added for Github Issue #21 - set the overflow text to
      // match the tooltip when showtext (for the button) is false
      if(!showtext && cnf.tooltip) {
        cnf.overflowText = cnf.tooltip;
      }
			
			return new Ext.Button(cnf);
		};
		
		store.allSaveCompleted = function() {
			var completed = true;
			store.eachTiedChild(function(s) {
				if(s.save_inprogress) { completed = false; }
			});
			return completed;
		};
		
		store.fireIfSaveAll = function() {
			if(store.allSaveCompleted()) { 
				store.fireEvent('saveall');
				var pstore = store.getParentStore();
				if(pstore) {
					pstore.fireIfSaveAll();
				}
			}
		};
		
		
		// -- This function purges out a list of param names from lastOptions 
		// and baseParams. This is still a major problem with the way stores
		// and various plugins operate:
		store.purgeParams = function(names) {
			Ext.each(names,function(name){
				if(store.baseParams[name]) { 
					delete store.baseParams[name]; 
				}
				if(store.lastOptions && store.lastOptions.params) {
					if(store.lastOptions.params[name]) { 
						delete store.lastOptions.params[name]; 
					}
				}
			},this);
		};
		// --
		
		store.addEvents('saveall');
		store.on('beforesave',function(ds,data) {
			store.save_inprogress = true; 
			
			// ------------------------------------
			// vv ----- CONFIRM ON DESTROY ----- vv
			if(data && data.destroy && data.destroy.length > 0 && plugin.cmp.confirm_on_destroy) {
				if(store.destroy_confirmed) {
					store.destroy_confirmed = false;
				}
				else {
					Ext.Msg.show({
						title: 'Confirm Delete?',
						msg: '<b>Are you sure you want to delete <span style="color:red;">' + 
							data.destroy.length + '</span> items?</b>',
						icon: Ext.Msg.WARNING,
						buttons: { yes: 'Yes', no: 'No' }, 
						fn: function(sel) {
							if (sel == 'yes') {
								this.destroy_confirmed = true;
								return this.saveAll();
							}
							else {
								this.destroy_confirmed = false; //<-- redundant, added for extra safety
								return this.undoChangesAll();
							}
						},
						scope: store
					});
					
					store.save_inprogress = false;
					return false;
				}
			}
			store.destroy_confirmed = false; //<-- clear one more time for good measure
			// ^^ ------------------------------ ^^
			// ------------------------------------
			
		});
		this.cmp.on('afterrender',function(){
			store.eachTiedChild(function(s) {
				s.on('save',function() {
					s.save_inprogress = false;
					store.fireIfSaveAll();
				});
			});
		});
		
    // Removed this exception hook because it is redundant and can cause
    // problems when rolling back certain changes. The store already fully
    // handles reverting itself when a save/persist operation fails.
    // Fixes Github Issue #11
    //store.on('exception',store.undoChanges,store);
    store.on('exception',function(ds,res,action){
      // NEW/UPDATE from #11 change above:
      // it turns out the undoChanges call wasn't so redundant after all, and
      // removing it caused the regression described in GitHub Issue #32.
      // The store *does* automatically roll itself back for update/delete,
      // but not for 'create' so now we call it specifically for that case.
      // This fixes #32, and keeps #11 fixed.
      if(action == 'create') {
        store.undoChanges.call(store);
      }
    },store);
	},
	
	// Only applies to Editor Grids implementing the 'beforeedit' event
	beforeCellEdit: function(e) {
		var column = e.grid.getColumnModel().getColumnById(e.column);
		if(!column) { return; }
		
		// Adding a new record (phantom):
		if(e.record.phantom) {
			// Prevent editing if allow_add is set to false:
			if(typeof column.allow_add !== "undefined" && !column.allow_add) {
				e.cancel = true; //<-- redundant with return false but set for good measure
				return false;
			}
		}
		// Editing an existing record:
		else {
			// Prevent editing if allow_edit is set to false:
			if(typeof column.allow_edit !== "undefined" && !column.allow_edit) {
				e.cancel = true; //<-- redundant with return false but set for good measure
				return false;
			}
		}
		
	},
	
	getStoreButton: function(name,showtext) {
		
		if(this.exclude_btn_map[name]) { return; }
		
		if(!this.cmp.loadedStoreButtons[name]) {
			var constructor = this.getStoreButtonConstructors.call(this)[name];
			if(! constructor) { return; }
			
			var cnf = this.store_button_cnf[name] || {};
				
			if(cnf.text && !cnf.tooltip) { cnf.tooltip = cnf.text; }
			if(typeof cnf.showtext != "undefined") { showtext = cnf.showtext; }
			
			var btn = constructor(cnf,this.cmp,showtext);
			if(!btn) { return; }
			
			this.cmp.loadedStoreButtons[name] = btn;
			
			// --------------------------------------------------
			// --- Keyboard shortcut handling:
			var keyMapConfigs = {
				'save': {
					ctrl: true,
					key: 's'
				},
				'undo': {
					ctrl: true,
					key: 'z'
				},
				'delete': {
					key: Ext.EventObject.DELETE
				}
			};
			
			this.storeBtnKeyMaps = this.storeBtnKeyMaps || {};
			
			if(keyMapConfigs[name]) {
				this.storeBtnKeyMaps[name] = new Ext.KeyMap(Ext.getBody(),Ext.apply({
					fn: function(k,e){
					
						// -- New: skip DELETE (46) if the event target is within a form field:
						if(k == 46 && e.target && typeof e.target.form != 'undefined') {
							return;
						}
						// --
					
						var El = this.cmp.getEl();
						var pos = El.getXY();
						
						// Method to know if our component element is *really* visible
						// and only handle the key event if it is
						var element = document.elementFromPoint(pos[0],pos[1]);
						
						if(El.isVisible() && El.contains(element)){
							e.stopEvent();
							btn.handler.call(this,btn);
						}
					},
					scope: this
				},keyMapConfigs[name]));
				
				this.cmp.on('beforedestroy',function(){
					this.storeBtnKeyMaps[name].disable.call(this.storeBtnKeyMaps[name]);
					delete this.storeBtnKeyMaps[name];
				},this);
			}
			// ---
			// --------------------------------------------------
			
		}

		return this.cmp.loadedStoreButtons[name];
	},
	
	getStoreButtonConstructors: function() {
		var plugin = this;
		return {
			add: function(cnf,cmp,showtext) {
				
				if(!cmp.store.api.create) { return false; }
				
				var btn = cmp.store.buttonConstructor(Ext.apply({
					tooltip: 'Add',
					iconCls: 'ra-icon-add',
					handler: function(btn) {
						var store = cmp.store;
						if(store.proxy.getConnection().isLoading()) { return; }
						if(cmp.use_add_form) {
							store.addRecordForm();
						}
						else {
							store.addRecord();
						}
					}
				},cnf || {}),showtext);
					
				cmp.store.addTrackedToggleFunc(function(store) {
					if (store.addNotAllowed()) {
						btn.setDisabled(true);
					}
					else {
						btn.setDisabled(false);
					}
				});
					
				return btn;
			},
			
			edit: function(cnf,cmp,showtext) {
				
				if(!cmp.store.api.update) { return false; }
				
				var btn = cmp.store.buttonConstructor(Ext.apply({
					tooltip: 'Edit',
					iconCls: 'ra-icon-application-form-edit',
					handler: function(btn) {
						var store = cmp.store;
						if(store.proxy.getConnection().isLoading()) { return; }
						store.editRecordForm();
					}
				},cnf || {}),showtext);
					
				cmp.store.addTrackedToggleFunc(function(store) {
					btn.setDisabled(store.editNotAllowed());
				});
				
				cmp.on('afterrender',function() {
					var store = this.store;
					var toggleBtn = function() {
						btn.setDisabled(store.editNotAllowed());
					};
					this.on('selectionchange',toggleBtn,this);
				},cmp);
					
				return btn;
			},
			
			'delete': function(cnf,cmp,showtext) {
				
				if(!cmp.store.api.destroy) { return false; }
				
				var btn = cmp.store.buttonConstructor(Ext.apply({
					tooltip: 'Delete',
					iconCls: 'ra-icon-delete',
					disabled: true,
					handler: function(btn) {
						var store = cmp.store;
						if(store.proxy.getConnection().isLoading()) { return; }
						//store.remove(cmp.getSelectionModel().getSelections());
						store.removeRecord(cmp.getSelectedRecords());
						//store.saveIfPersist();
						//if(cmp.persist_immediately) { store.save(); }
					}
				},cnf || {}),showtext);
				
				cmp.on('afterrender',function() {
				
					var toggleBtn = function() {
						if (this.getSelectedRecords.call(this).length > 0) {
							btn.setDisabled(false);
						}
						else {
							btn.setDisabled(true);
						}
					};
					
					this.on('selectionchange',toggleBtn,this);
				},cmp);
					
				return btn;
			},
			
      // Note: this is *not* the refresh button in the grid toolbar/pager because
      // it already provides its own
			reload: function(cnf,cmp,showtext) {
				
				return cmp.store.buttonConstructor(Ext.apply({
					tooltip: 'Reload',
					iconCls: 'x-tbar-loading',
					handler: function(btn) {
						var store = cmp.store;
						store.reloadAll();
					}
				},cnf || {}),showtext);
			},
			
			save: function(cnf,cmp,showtext) {

				if(cmp.persist_all_immediately) { return false; }
				
				var btn = cmp.store.buttonConstructor(Ext.apply({
					tooltip: 'Save',
					iconCls: 'ra-icon-save-ok',
					disabled: true,
					handler: function(btn) {
						var store = cmp.store;
						//store.save();
						store.saveAll();
					}
				},cnf || {}),showtext);
					
				var title_parent = cmp.findParentBy(function(c){
					return (c.title && c.setTitle)  ? true : false;
				},this);

				var modified_suffix = '&nbsp;<span class="ra-tab-dirty-flag">*</span>';

				cmp.cascade(function(){
					if(!this.store || !this.store.addTrackedToggleFunc){ return; }
					this.store.addTrackedToggleFunc(function(store) {
						var has_changes = cmp.store.hasAnyPendingChanges();
						btn.setDisabled(!has_changes);
						
						// ---- Add/remove '*' suffix from the title based on the saved/unsaved status:
						if(!title_parent) { return; }
						if(has_changes) {
							if(!title_parent.notUnsavedTitle) {
								title_parent.notUnsavedTitle = title_parent.title;
								title_parent.setTitle(title_parent.notUnsavedTitle + modified_suffix);
							}
						}
						else {
							if(title_parent.notUnsavedTitle) {
								title_parent.setTitle(title_parent.notUnsavedTitle);
								delete title_parent.notUnsavedTitle;
							}
						}
						// ----
					});
				});
				
				return btn;
			},
			
			undo: function(cnf,cmp,showtext) {

				if(cmp.persist_all_immediately) { return false; }
				
				var btn = cmp.store.buttonConstructor(Ext.apply({
					tooltip: 'Undo',
					iconCls: 'ra-icon-arrow-undo',
					disabled: true,
					handler: function(btn) {
						var store = cmp.store;
						//store.undoChanges.call(store);
						store.undoChangesAll.call(store);
					}
				},cnf || {}),showtext);
				
					
				cmp.cascade(function(){
					if(!this.store || !this.store.addTrackedToggleFunc){ return; }
					this.store.addTrackedToggleFunc(function(store) {
						if (cmp.store.hasAnyPendingChanges()) {
							btn.setDisabled(false);
						}
						else {
							btn.setDisabled(true);
						}
					});
				});
					
				/*
				cmp.store.addTrackedToggleFunc(function(store) {
					if (store.hasPendingChanges()) {
						btn.setDisabled(false);
					}
					else {
						btn.setDisabled(true);
					}
				});
				*/
					
				return btn;
			}
		};
	},
	
	insertStoreButtonsBbar: function() {
		var index = 0;
		var skip_reload = false;
		var bbar;

		if(Ext.isFunction(this.cmp.getBottomToolbar)) { 
			bbar = this.cmp.getBottomToolbar();
		}
		else if (Ext.isFunction(this.cmp.ownerCt.getBottomToolbar)) {
			bbar = this.cmp.ownerCt.getBottomToolbar();
		}
		
		if(!bbar) { return; }
		
		bbar.items.each(function(cmp,indx) {
			if(cmp.tooltip == 'Refresh') { 
				index = indx + 1; 
				skip_reload = true;
			};
		});
		
		//console.dir(bbar);
		
		var showtext = false;
		if(this.show_store_button_text) { showtext = true; }
		
		var bbar_items = [];
		Ext.each(this.store_buttons,function(btn_name) {
			// Skip redundant reload if we have a paging toolbar
			if(btn_name == 'reload' && skip_reload) { return; }
			
			var btn = this.getStoreButton(btn_name,showtext);
			if(!btn) { return; }
			bbar_items.unshift(btn);
		},this);
		Ext.each(bbar_items,function(btn) { bbar.insert(index,btn); },this);
		
	},
	
	insertStoreButtonsTbar: function() {
		var tbar;

		if(Ext.isFunction(this.cmp.getTopToolbar)) { 
			tbar = this.cmp.getTopToolbar();
		}
		else if (Ext.isFunction(this.cmp.ownerCt.getTopToolbar)) {
			tbar = this.cmp.ownerCt.getTopToolbar();
		}
		
		if(!tbar) { return; }
		
		var showtext = false;
		if(this.show_store_button_text) { showtext = true; }
		
		var tbar_items = [ '->' ]; //<-- right-align buttons
		Ext.each(this.store_buttons,function(btn_name) {
			var btn = this.getStoreButton(btn_name,showtext);
			if(!btn) { return; }
			tbar_items.unshift(btn);
		},this);
		Ext.each(tbar_items,function(btn) { tbar.insert(0,btn); },this);
		
	},
	
	beforeRemoveConfirm: function(c,component) {
		if(component != this.cmp) {
			var parent = this.cmp.findParentBy(function(p) {
				if(p.confirmRemoveInProg) { return false; }
				
				if(p == component) { return true; }
				// if we're here, it's a sibling removal:
				return false;
			},this);
			// This is a sibling removal, or our tied parent already handled the remove, which we need to ignore:
			if(component != parent) { return true; }
		}
		
		component.confirmRemoveInProg = true;
		
		var store = this.cmp.store;
		if(!store || !store.hasAnyPendingChanges()) { 
			c.un('beforeremove',this.beforeRemoveConfirm,this);
			return true; 
		}
		
		Ext.Msg.show({
			title: 'Save Changes?',
			msg: (
				store.removed.length > 0 ?
					'<b>There are unsaved changes on this page, including <span style="color:red;">' + 
						store.removed.length + '</span> items to be deleted.</b>' :
					'<b>There are unsaved changes on this page.</b>'
				) +
				'<br><br>Save before closing?<br>',
			icon: Ext.Msg.WARNING,
			buttons: { yes: 'Save', no: 'Discard Changes', cancel: 'Cancel' }, 
			fn: function(sel) {
				if (sel == 'cancel') {
					delete component.confirmRemoveInProg;
					return;
				}
				else if (sel == 'yes') {
					var onsave;
					onsave = function() {
						store.un('saveall',onsave);
						c.un('beforeremove',this.beforeRemoveConfirm,this);
						// Complete the original remove:
						c.remove(component);
					};
					store.on('saveall',onsave);
					// Prevent the confirm delete dialog from also being displayed:
					store.destroy_confirmed = true;
					store.saveAll();
				}
				else {
					store.undoChangesAll();
					c.un('beforeremove',this.beforeRemoveConfirm,this);
					// Complete the original remove:
					c.remove(component);
				};
			},
			scope: this
		});
		
		return false;
	},
	
	getAddFormPanel: function(newRec,close_handler,callback) {
		
		var plugin = this;
		var store = this.cmp.store;
		
		close_handler = close_handler || Ext.emptyFn;
		
		var cancel_handler = function(btn) {
			close_handler(btn);
		};
		
		var save_handler = function(btn) {
			var fp = btn.ownerCt.ownerCt, form = fp.getForm();
			
			// Disable the form panel to prevent user interaction during the save.
			// Tthere is also a global load mask set on updates, but it is possible 
			// that the form could be above it if this is a chained sequences of
			// created records, so this is an extra safety measure in that case:
			fp.setDisabled(true);
			
			// Re-enable the form panel if an exception occurs so the user can
			// try again. We don't need to do this on success because we close
			// the form/window:
			var fp_enable_handler = function(){ try{ fp.setDisabled(false); }catch(err){} }
			store.on('exception',fp_enable_handler,this);
			
			// Use a copy of the new record in case the save fails and we need to try again:
			var newRecord = newRec.copy();
			newRecord.phantom = true; //<-- the copy doesn't have this set like the original... why?
			
			form.updateRecord(newRecord);
			
			store.add(newRecord);
			
			if(plugin.persist_immediately.create) {
				
				var after_write_fn = Ext.emptyFn;
				var remove_handler = Ext.emptyFn;
				
				remove_handler = function() { 
					store.un('write',after_write_fn);
					// Remove ourselves as we are also a single-use handler:
					store.un('exception',remove_handler);
					// remove the enable handler:
					store.un('exception',fp_enable_handler);
				}
				
				after_write_fn = function(store,action) {
					if(action == 'create') {
						// Remove ourselves as we are a single-use handler:
						remove_handler();
						
						// close the add form only after successful create on the server:
						close_handler(btn);
					}
				}
				
				store.on('write',after_write_fn,store);
				
				// Also remove this single-use handler on exception:
				store.on('exception',remove_handler,store);
				
				store.saveIfPersist(); 
			}
			else {
				close_handler(btn);
			}
		};
		
		//var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Loading Form..."});
		var myMask = new Ext.LoadMask(plugin.cmp.getEl(), {msg:"Loading Add Form..."});
		var show_mask = function() { myMask.show(); }
		var hide_mask = function() { myMask.hide(); }
		
		var params = {};
		if(store.lastOptions.params) { Ext.apply(params,store.lastOptions.params); }
		if(store.baseParams) { Ext.apply(params,store.baseParams); }
		if(plugin.cmp.baseParams) { Ext.apply(params,plugin.cmp.baseParams); }
		Ext.apply(params,plugin.add_form_url_params);
		
		show_mask();
		Ext.Ajax.request({
			url: plugin.cmp.add_form_url,
			params: params,
			failure: hide_mask,
			success: function(response,options) {
				
				var formpanel = Ext.decode(response.responseText);
				
				Ext.each(formpanel.items,function(field) {
					// Important: autoDestroy must be false on the store or else store-driven
					// components (i.e. combos) will be broken as soon as the form is closed 
					// the first time
					if(field.store) { field.store.autoDestroy = false; }
          // Make sure that hidden fields that can't be changed don't 
          // block validation of the form if they are empty and erroneously
          // set with allowBlank: false (common-sense failsafe):
          if(field.hidden) { field.allowBlank = true; } 
				},this);
				
				Ext.each(formpanel.buttons,function(button) {
					if(button.name == 'save') {
						button.handler = save_handler;
					}
					else if(button.name == 'cancel') {
						button.handler = cancel_handler;
					}
				},this);
				
				formpanel.Record = newRec;
				
				hide_mask();
				callback(formpanel);
			},
			scope: this
		});
	},
	
	
	getEditFormPanel: function(Rec,close_handler,callback) {
		
		var plugin = this;
		var store = this.cmp.store;
		
		close_handler = close_handler || Ext.emptyFn;
		
		var cancel_handler = function(btn) {
			close_handler(btn);
		};
		
		var save_handler = function(btn) {
			var fp = btn.ownerCt.ownerCt, form = fp.getForm();
			
			// Disable the form panel to prevent user interaction during the save.
			// Tthere is also a global load mask set on updates, but it is possible 
			// that the form could be above it if this is a chained sequences of
			// created records, so this is an extra safety measure in that case:
			fp.setDisabled(true);
			
			// Re-enable the form panel if an exception occurs so the user can
			// try again. We don't need to do this on success because we close
			// the form/window:
			var fp_enable_handler = function(){ try{ fp.setDisabled(false); }catch(err){} }
			store.on('exception',fp_enable_handler,this);
			
			form.updateRecord(Rec);
			
			if(plugin.persist_immediately.update) {
				
				var after_write_fn = Ext.emptyFn;
				var remove_handler = Ext.emptyFn;
				
				remove_handler = function() { 
					store.un('write',after_write_fn);
					// Remove ourselves as we are also a single-use handler:
					store.un('exception',remove_handler);
					// remove the enable handler:
					store.un('exception',fp_enable_handler);
				}
				
				after_write_fn = function(store,action) {
					if(action == 'update') {
						// Remove ourselves as we are a single-use handler:
						remove_handler();
						
						// close the add form only after successful create on the server:
						close_handler(btn);
					}
				}
				
				store.on('write',after_write_fn,store);
				
				// Also remove this single-use handler on exception:
				store.on('exception',remove_handler,store);
				
				if(store.hasAnyPendingChanges()) {
					store.saveIfPersist();
				}
				else {
					// Cleanup if there are no changes, thus no write action will be
					// called. 
					remove_handler();
					close_handler(btn);
				}
			}
			else {
				close_handler(btn);
			}
		};
		
		//var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Loading Form..."});
		var myMask = new Ext.LoadMask(plugin.cmp.getEl(), {msg:"Loading Edit Form..."});
		var show_mask = function() { myMask.show(); }
		var hide_mask = function() { myMask.hide(); }
		
		var params = {};
		if(store.lastOptions.params) { Ext.apply(params,store.lastOptions.params); }
		if(store.baseParams) { Ext.apply(params,store.baseParams); }
		if(plugin.cmp.baseParams) { Ext.apply(params,plugin.cmp.baseParams); }
		Ext.apply(params,plugin.add_form_url_params);
		
		show_mask();
		Ext.Ajax.request({
			url: plugin.cmp.edit_form_url,
			params: params,
			failure: hide_mask,
			success: function(response,options) {

				var formpanel = Ext.decode(response.responseText);
				
				var new_items = [];
				Ext.each(formpanel.items,function(field) {
					// Don't try to edit fields that aren't loaded, exclude them from the form:
					if(!store.hasLoadedColumn(field.name)){ return; }
					// Important: autoDestroy must be false on the store or else store-driven
					// components (i.e. combos) will be broken as soon as the form is closed 
					// the first time
					if(field.store) { field.store.autoDestroy = false; }
          // Make sure that hidden fields that can't be changed don't 
          // block validation of the form if they are empty and erroneously
          // set with allowBlank: false (common-sense failsafe):
          if(field.hidden) { field.allowBlank = true; } 
					field.value = Rec.data[field.name];
					new_items.push(field);
				},this);
				formpanel.items = new_items;
				
				Ext.each(formpanel.buttons,function(button) {
					if(button.name == 'save') {
						button.handler = save_handler;
					}
					else if(button.name == 'cancel') {
						button.handler = cancel_handler;
					}
				},this);
				
				formpanel.Record = Rec;
				
				hide_mask();
				callback(formpanel);
			},
			scope: this
		});
	}
	// --- ^^ ---

});
Ext.preg('datastore-plus',Ext.ux.RapidApp.Plugin.CmpDataStorePlus);


Ext.ns('Ext.ux.RapidApp.Plugin');

/* disabled and replaced by "listener_callbacks"

// Generic plugin that loads a list of event handlers. These 
// should be passed as an array of arrays, where the first
// element of each inner array is the event name, and the rest
// of the items are the handlers (functions) to register

Ext.ux.RapidApp.Plugin.EventHandlers = Ext.extend(Ext.util.Observable,{

	init: function(cmp) {
		if (! Ext.isArray(cmp.event_handlers)) { return true; }
		
		Ext.each(cmp.event_handlers,function(item) {
			if (! Ext.isArray(item)) { throw "invalid element found in event_handlers (should be array of arrays)"; }
			
			var event = item.shift();
			Ext.each(item,function(handler) {
				//Add handler:
				cmp.on(event,handler);
			});
		});
	}
});
Ext.preg('rappeventhandlers',Ext.ux.RapidApp.Plugin.EventHandlers);
*/


/* 2011-03-25 by HV:
 This is my solution to the problem described here:
 http://www.sencha.com/forum/showthread.php?92215-Toolbar-resizing-problem
*/
Ext.ux.RapidApp.Plugin.AutoWidthToolbars = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		if(! cmp.getTopToolbar) { return; }
		cmp.on('afterrender',function(c) {
			var tbar = c.getTopToolbar();
			if(tbar) {
				this.setAutoSize(tbar);
			}
			var bbar = c.getBottomToolbar();
			if(bbar) {
				this.setAutoSize(bbar);
			}
		},this);
	},
	setAutoSize: function(toolbar) {
		var El = toolbar.getEl();
		El.setSize('auto');
		El.parent().setSize('auto');
	}
});
Ext.preg('autowidthtoolbars',Ext.ux.RapidApp.Plugin.AutoWidthToolbars);


Ext.ux.RapidApp.Plugin.ClickableLinks = Ext.extend(Ext.util.Observable, {
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	init: function(cmp){
		this.cmp = cmp;
		
		// if HtmlEditor:
		if(this.cmp.onEditorEvent) {
			var onEditorEvent_orig = this.cmp.onEditorEvent;
			var plugin = this;
			this.cmp.onEditorEvent = function(e) {
				if(e.type == 'click') {  plugin.onClick.apply(this,arguments);  }
				onEditorEvent_orig.apply(this,arguments);
			}
		}
		else {
			this.cmp.on('render', this.onRender, this);
		}
	},
	
	onRender: function() {
		var el = this.cmp.getEl();

		Ext.EventManager.on(el, {
			'click': this.onClick,
			buffer: 100,
			scope: this
		});		
	},
	
	onClick: function(e,el,o) {
		// -- New: limit to only Anchor (<a>) tags. Needed for IE
		var tag = el ? el.nodeName : null;
		if(tag != 'A') { return; }
		// --
		
		var Element = new Ext.Element(el);
		var href = Element.getAttribute('href');
		if (href && href != '#') {
			document.location.href = href;
		}
	}
});
Ext.preg('clickablelinks',Ext.ux.RapidApp.Plugin.ClickableLinks);


Ext.ns('Ext.ux.RapidApp.Plugin.Combo');
Ext.ux.RapidApp.Plugin.Combo.Addable = Ext.extend(Ext.util.Observable, {
	
	createItemClass: 'appsuperbox-create-item',
	createItemHandler: null,
	
	constructor: function(cnf) { 	
		Ext.apply(this,cnf); 	
	},
	
	init: function(cmp){
		this.cmp = cmp;
		
		Ext.apply(this.cmp,{ classField: 'cls' });
		
		Ext.copyTo(this,this.cmp,[
			'createItemClass',
			'createItemHandler',
			'default_cls'
		]);
		
		Ext.applyIf(this.cmp,{
			tpl: this.getDefaultTpl()
		});
		
		if(Ext.isString(this.cmp.tpl)) {
			var tpl = this.getTplPrepend() + this.cmp.tpl;
			this.cmp.tpl = tpl;
		}
		
		if (this.createItemHandler) {
			this.initCreateItems();
		}
	},
		
	initCreateItems: function() {
		var plugin = this;
		this.cmp.onViewClick = function() {
			var event = arguments[arguments.length - 1]; // <-- last passed argument, the event object;
			if(event) {
				var target = event.getTarget(null,null,true);
				
				// Handle create item instead of normal handler:
				if (target.hasClass(plugin.createItemClass)) {
					this.collapse();
					return plugin.createItemHandler.call(this,plugin.createItemCallback);
				}
			}

			// Original handler:
			this.constructor.prototype.onViewClick.apply(this,arguments);
		};
	},
	
	createItemCallback: function(data) {
		if(! data[this.classField] && this.default_cls) {
			data[this.classField] = this.default_cls;
		}
		var Store = this.getStore();
		var recMaker = Ext.data.Record.create(Store.fields.items);
		var newRec = new recMaker(data);
		Store.insert(0,newRec);
		this.onSelect(newRec,0);
	},
	
	getTplPrepend: function() {
		return '<div style="float:right;"><div class="' + this.createItemClass + '">Create New</div></div>';
	},
	
	getDefaultTpl: function() {
		return 
			'<div class="x-combo-list-item">shit &nbsp;</div>' +
			'<tpl for="."><div class="x-combo-list-item">{' + this.cmp.displayField + '}</div></tpl>';
	}
});
Ext.preg('combo-addable',Ext.ux.RapidApp.Plugin.Combo.Addable);

Ext.ux.RapidApp.Plugin.Combo.AppSuperBox = Ext.extend(Ext.ux.RapidApp.Plugin.Combo.Addable, {
	
	itemLabel: 'items',
	
	init: function(cmp){
		this.cmp = cmp;
		
		if(this.cmp.fieldLabel) { this.itemLabel = this.cmp.fieldLabel; }
		
		Ext.apply(this.cmp,{
			xtype: 'superboxselect', // <-- no effect, the xtype should be set to this in the consuming class
			extraItemCls: 'x-superboxselect-x-flag',
			expandBtnCls: 'ra-icon-roles_expand_sprite',
			listEmptyText: '(no more available ' + this.itemLabel + ')',
			emptyText: '(none)',
			listAlign: 'tr?',
			itemSelector: 'li.x-superboxselect-item',
			stackItems: true
		});
		
		Ext.ux.RapidApp.Plugin.Combo.AppSuperBox.superclass.init.apply(this,arguments);
	},
	
	getDefaultTpl: function() {
		return new Ext.XTemplate(
			'<div class="x-superboxselect x-superboxselect-stacked">',
					'<ul>',
						'<li style="padding-bottom:2px;">',
							'<div style="float:right;"><div class="' + this.createItemClass + '">Create New</div></div>',
							'<div>',
								'Add ' + this.itemLabel + ':',
							'</div>',
						'</li>',
						'<tpl for=".">',
							'<li class="x-superboxselect-item x-superboxselect-x-flag {' + this.cmp.classField + '}">',
								'{' + this.cmp.displayField + '}',
							'</li>',
						'</tpl>',
					'</ul>',
				'</div>'
		);
	}
	
});
Ext.preg('appsuperbox',Ext.ux.RapidApp.Plugin.Combo.AppSuperBox);


Ext.ns('Ext.ux.RapidApp.Plugin');
Ext.ux.RapidApp.Plugin.ReloadServerEvents = Ext.extend(Ext.util.Observable, {
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	init: function(cmp){
		this.cmp = cmp;
		if(Ext.isArray(this.cmp.reloadEvents)) {
			this.cmp.on('render', this.onRender, this);
		}
	},
	
	onRender: function() {

		var handler = {
			id: this.cmp.id,
			func: function() {
				var store = null;
				if(Ext.isFunction(this.getStore)) { store = this.getStore(); }
				if(! store) { store = this.store; }
				if(store && Ext.isFunction(store.reload)) {
					store.reload();
				}
			}
		};
		
		Ext.each(this.cmp.reloadEvents,function(event) {
			Ext.ux.RapidApp.EventObject.attachServerEvents(handler,event);
		});
	}
	
});
Ext.preg('reload-server-events',Ext.ux.RapidApp.Plugin.ReloadServerEvents);


/*  Modified from Ext.ux.grid.Search for appgrid2 */

// Check RegExp.escape dependency
if('function' !== typeof RegExp.escape) {
	throw('RegExp.escape function is missing. Include Ext.ux.util.js file.');
}

/**
 * Creates new Search plugin
 * @constructor
 * @param {Object} A config object
 */
//Ext.ux.RapidApp.Plugin.GridQuickSearch = function(config) {
//	Ext.apply(this, config);
//	Ext.ux.RapidApp.Plugin.GridQuickSearch.superclass.constructor.call(this);
//}; // eo constructor

Ext.ux.RapidApp.Plugin.GridQuickSearch = Ext.extend(Ext.util.Observable, {
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	/**
	 * @cfg {Boolean} autoFocus Try to focus the input field on each store load if set to true (defaults to undefined)
	 */

	/**
	 * @cfg {String} searchText Text to display on menu button
	 */
	 searchText:'Quick Search'

	/**
	 * @cfg {String} searchTipText Text to display as input tooltip. Set to '' for no tooltip
	 */ 
	,searchTipText:'Type a text to search and press Enter'

	/**
	 * @cfg {String} selectAllText Text to display on menu item that selects all fields
	 */
	,selectAllText:'Select All'

	/**
	 * @cfg {String} position Where to display the search controls. Valid values are top and bottom
	 * Corresponding toolbar has to exist at least with mimimum configuration tbar:[] for position:top or bbar:[]
	 * for position bottom. Plugin does NOT create any toolbar.(defaults to "bottom")
	 */
	,position:'bottom'

	/**
	 * @cfg {String} iconCls Icon class for menu button (defaults to "ra-icon-magnifier")
	 */
	//,iconCls:'ra-icon-magnifier'
	,iconCls: null

	/**
	 * @cfg {String/Array} checkIndexes Which indexes to check by default. Can be either 'all' for all indexes
	 * or array of dataIndex names, e.g. ['persFirstName', 'persLastName'] (defaults to "all")
	 */
	,checkIndexes:'all'

	/**
	 * @cfg {Array} disableIndexes Array of index names to disable (not show in the menu), e.g. ['persTitle', 'persTitle2']
	 * (defaults to [] - empty array)
	 */
	,disableIndexes:[]

	/**
	 * Field containing search text (read-only)
	 * @property field
	 * @type {Ext.form.TwinTriggerField}
	 */

	/**
	 * @cfg {String} dateFormat How to format date values. If undefined (the default) 
	 * date is formatted as configured in colummn model
	 */

	/**
	 * @cfg {Boolean} showSelectAll Select All item is shown in menu if true (defaults to true)
	 */
	,showSelectAll:true

	/**
	 * Menu containing the column module fields menu with checkboxes (read-only)
	 * @property menu
	 * @type {Ext.menu.Menu}
	 */

	/**
	 * @cfg {String} menuStyle Valid values are 'checkbox' and 'radio'. If menuStyle is radio
	 * then only one field can be searched at a time and selectAll is automatically switched off. 
	 * (defaults to "checkbox")
	 */
	,menuStyle:'checkbox'

	/**
	 * @cfg {Number} minChars Minimum characters to type before the request is made. If undefined (the default)
	 * the trigger field shows magnifier icon and you need to click it or press enter for search to start. If it
	 * is defined and greater than 0 then maginfier is not shown and search starts after minChars are typed.
	 * (defaults to undefined)
	 */

	/**
	 * @cfg {String} minCharsTipText Tooltip to display if minChars is > 1
	 */
	,minCharsTipText:'Type at least {0} characters'

	/**
	 * @cfg {String} mode Use 'remote' for remote stores or 'local' for local stores. If mode is local
	 * no data requests are sent to server the grid's store is filtered instead (defaults to "remote")
	 */
	,mode:'remote'

	/**
	 * @cfg {Array} readonlyIndexes Array of index names to disable (show in menu disabled), e.g. ['persTitle', 'persTitle2']
	 * (defaults to undefined)
	 */

	/**
	 * @cfg {Number} width Width of input field in pixels (defaults to 100)
	 */
	,width:250

	/**
	 * @cfg {String} xtype xtype is usually not used to instantiate this plugin but you have a chance to identify it
	 */
	,xtype:'gridsearch'

	/**
	 * @cfg {Object} paramNames Params name map (defaults to {fields:"fields", query:"query"}
	 */
	,paramNames: {
		fields:'fields',
		query:'query',
		quicksearch_mode: 'quicksearch_mode'
	}

	/**
	 * @cfg {String} shortcutKey Key to fucus the input field (defaults to r = Sea_r_ch). Empty string disables shortcut
	 */
	,shortcutKey:'r'

	/**
	 * @cfg {String} shortcutModifier Modifier for shortcutKey. Valid values: alt, ctrl, shift (defaults to "alt")
	 */
	,shortcutModifier:'alt'

	/**
	 * @cfg {String} align "left" or "right" (defaults to "left")
	 */

	/**
	 * @cfg {Number} minLength Force user to type this many character before he can make a search 
	 * (defaults to undefined)
	 */

	/**
	 * @cfg {Ext.Panel/String} toolbarContainer Panel (or id of the panel) which contains toolbar we want to render
	 * search controls to (defaults to this.grid, the grid this plugin is plugged-in into)
	 */
	
	// {{{
	/**
	 * @private
	 * @param {Ext.grid.GridPanel/Ext.grid.EditorGrid} grid reference to grid this plugin is used for
	 */
	// ,fieldNameMap: {},
	
	,init:function(grid) {
		this.grid = grid;
		
		Ext.apply(this,grid.grid_search_cnf || {});
		
		grid.quicksearch_plugin = this;
		
		// -- New: disable plugin if there are no quick_search columns (2012-04-11 by HV)
		if(!this.getQuickSearchColumns().length > 0) { return; }
		// --
		
		this.fieldNameMap = {};
		
		// -- query_search_use_column support, added by HV 2011-12-26
		var columns = this.grid.initialConfig.columns;
		Ext.each(columns,function(column) {
			if(column.query_search_use_column){
				this.fieldNameMap[column.name] = column.query_search_use_column;
			}
		},this);
		// --

		// setup toolbar container if id was given
		if('string' === typeof this.toolbarContainer) {
			this.toolbarContainer = Ext.getCmp(this.toolbarContainer);
		}

		// do our processing after grid render and reconfigure
		grid.onRender = grid.onRender.createSequence(this.onRender, this);
		grid.reconfigure = grid.reconfigure.createSequence(this.reconfigure, this);
	}, // eo function init
	// }}}
	// {{{
	/**
	 * adds plugin controls to <b>existing</b> toolbar and calls reconfigure
	 * @private
	 */
	onRender:function() {
	
		var panel = this.toolbarContainer || this.grid;
		var tb = 'bottom' === this.position ? panel.bottomToolbar : panel.topToolbar;
		
		// If there is no toolbar, then we can't setup, so we abort:
		if(!tb) { return; }
		
		// -- Optional extra override for checked columns. init_quick_search_columns is
		// set in AppGrid2 via HTTP Query param 'quick_search_cols'
		var store = this.grid.getStore();
		store.quickSearchCheckIndexes = this.grid.init_quick_search_columns || store.quickSearchCheckIndexes;
		// --
		
		store.quicksearch_mode = store.quicksearch_mode || this.grid.quicksearch_mode;
		this.grid.quicksearch_mode = store.quicksearch_mode;
		
		store.on('beforeload',this.applyStoreParams,this);
		
		// add menu
		this.menu = new Ext.menu.Menu();

		// handle position
		if('right' === this.align) {
			tb.addFill();
		}
		else {
			if(0 < tb.items.getCount()) {
				// The separator is ugly, removed (2012-04-11 by HV)
				//tb.addSeparator();
			}
		}
		
		this.grid.quicksearch_mode = this.grid.quicksearch_mode || 'like';
		
		this.searchText = (this.grid.quicksearch_mode == 'exact') ?
			'Exact Search' : this.searchText;
		
		this.modeMenu = new Ext.menu.Menu();
		this.modeMenu.add(
			{
				xtype: 'menucheckitem',
				text: 'Normal',
				group: 'quick_search_mode',
				header: 'Quick Search',
				mode: 'like',
				checked: (this.grid.quicksearch_mode == 'like' ? true : false)
			},
			{
				xtype: 'menucheckitem',
				text: 'Exact (faster)',
				group: 'quick_search_mode',
				header: 'Exact Search',
				mode: 'exact',
				checked: (this.grid.quicksearch_mode == 'exact' ? true : false)
			}
		);
		
		this.outerMenu = new Ext.menu.Menu();
		this.outerMenu.add(
			{
				text: 'Mode',
				iconCls: 'ra-icon-preferences',
				hideOnClick: false,
				menu: this.modeMenu
			},
			{
				text: 'Search Columns',
				iconCls: 'x-cols-icon',
				hideOnClick: false,
				menu: this.menu
			}
		);
		
		// Only enable the new 'outerMenu' if allow_set_quicksearch_mode is true:
		var menu = this.grid.allow_set_quicksearch_mode ? this.outerMenu : this.menu;
		
		var btnConfig = {
			text: this.searchText,
			menu: menu,
			iconCls:this.iconCls
		};
		
		
		//this.menu.on('hide',this.persistCheckIndexes,this);
		menu.on('hide',this.persistCheckIndexes,this);
		
		// -- Optional config: disable pressing of the button (making it act only as a label):
		if(this.grid.quicksearch_disable_change_columns) {
			delete btnConfig.menu;
			Ext.apply(btnConfig,{
				enableToggle: false,
				allowDepress: false,
				handleMouseEvents: false,
				cls:'ra-override-cursor-default'
			});
		}
		// --

		this.button = new Ext.Button(btnConfig);
		tb.add(this.button);

		// add input field (TwinTriggerField in fact)
		this.field = new Ext.form.TwinTriggerField({
			 width:this.width
			,selectOnFocus:undefined === this.selectOnFocus ? true : this.selectOnFocus
			,trigger1Class:'x-form-clear-trigger'
			,trigger2Class:this.minChars ? 'x-hide-display' : 'x-form-search-trigger'
			,onTrigger1Click:this.onTriggerClear.createDelegate(this)
			,onTrigger2Click:this.minChars ? Ext.emptyFn : this.onTriggerSearch.createDelegate(this)
			,minLength:this.minLength
		});
		
		// -----
		if(this.grid.preload_quick_search) {
			// -- Highlight Fx on the field to call attention to it when it is preloaded
			this.grid.on('firstload',function(){
				this.el.pause(.2);
				this.el.frame("FFDF00", 2, { duration: .5 });
			},this.field);
			// --
			
			this.field.setValue(this.grid.preload_quick_search);
			var plugin = this, store = this.grid.store;
			var onBeforeload;
			onBeforeload = function(ds,options) {
				// immediately remove ourselves on first call. This should be the initial
				// load of the store after being initialized
				store.un('beforeload',onBeforeload,plugin);
				// Called only on the very first load (removes itself above)
				// sets the store params without calling reload (since the load is
				// already in progress). This allows a single load, including the
				// preload_quick_search
				plugin.applyStoreParams.call(plugin);
				
			}
			store.on('beforeload',onBeforeload,plugin);
		}
		// -----

		// install event handlers on input field
		this.field.on('render', function() {
			// register quick tip on the way to search
			
			/*
			if((undefined === this.minChars || 1 < this.minChars) && this.minCharsTipText) {
				Ext.QuickTips.register({
					 target:this.field.el
					,text:this.minChars ? String.format(this.minCharsTipText, this.minChars) : this.searchTipText
				});
			}
			*/
			
			if(this.minChars) {
				this.field.el.on({scope:this, buffer:300, keyup:this.onKeyUp});
			}

			// install key map
			var map = new Ext.KeyMap(this.field.el, [{
				 key:Ext.EventObject.ENTER
				,scope:this
				,fn:this.onTriggerSearch
			},{
				 key:Ext.EventObject.ESC
				,scope:this
				,fn:this.onTriggerClear
			}]);
			map.stopEvent = true;
		}, this, {single:true});

		tb.add(this.field);

		// re-layout the panel if the toolbar is outside
		if(panel !== this.grid) {
			this.toolbarContainer.doLayout();
		}

		// reconfigure
		this.reconfigure();

		// keyMap
		if(this.shortcutKey && this.shortcutModifier) {
			var shortcutEl = this.grid.getEl();
			var shortcutCfg = [{
				 key:this.shortcutKey
				,scope:this
				,stopEvent:true
				,fn:function() {
					this.field.focus();
				}
			}];
			shortcutCfg[0][this.shortcutModifier] = true;
			this.keymap = new Ext.KeyMap(shortcutEl, shortcutCfg);
		}

		if(true === this.autoFocus) {
			this.grid.store.on({scope:this, load:function(){this.field.focus();}});
		}

	} // eo function onRender
	// }}}
	// {{{
	/**
	 * field el keypup event handler. Triggers the search
	 * @private
	 */
	,onKeyUp:function(e, t, o) {

		// ignore special keys 
		if(e.isNavKeyPress()) {
			return;
		}

		var length = this.field.getValue().toString().length;
		if(0 === length || this.minChars <= length) {
			this.onTriggerSearch();
		}
	} // eo function onKeyUp
	// }}}
	// {{{
	/**
	 * Clear Trigger click handler
	 * @private 
	 */
	,onTriggerClear:function() {
		// HV: added the baseParams check below. this fixes a bug, it is probably needed
		// because of odd things we're doing in AppGrid2/Store
		if(this.field.getValue() || this.grid.store.lastOptions.params.query || this.grid.store.baseParams.query) {
			this.field.setValue('');
			this.field.focus();
			this.onTriggerSearch();
			
		}
	} // eo function onTriggerClear
	// }}}
	// {{{
	/**
	 * Search Trigger click handler (executes the search, local or remote)
	 * @private 
	 */
	,onTriggerSearch:function() {
		if(!this.field.isValid()) {
			return;
		}
		var val = this.field.getValue();
		var store = this.grid.store;

		// grid's store filter
		if('local' === this.mode) {
			store.clearFilter();
			if(val) {
				store.filterBy(function(r) {
					var retval = false;
					this.menu.items.each(function(item) {
						if(!item.checked || retval) {
							return;
						}
						var rv = r.get(item.dataIndex);
						rv = rv instanceof Date ? rv.format(this.dateFormat || r.fields.get(item.dataIndex).dateFormat) : rv;
						var re = new RegExp(RegExp.escape(val), 'gi');
						retval = re.test(rv);
					}, this);
					if(retval) {
						return true;
					}
					return retval;
				}, this);
			}
			else {
			}
		}
		// ask server to filter records
		else {
			//applyStoreParams now called in 'beforeload' handler
			//this.applyStoreParams();
			// reload store
			
			// clear start (necessary if we have paging) - resets to page 1
			// (moved from applyStoreParams since it is now called in beforeload)
			if(store.lastOptions && store.lastOptions.params) {
				store.lastOptions.params[store.paramNames.start] = 0;
			}
			
			store.reload();
		}
	}

	,applyStoreParams: function() {
		var val = this.field.disabled ? '' : this.field.getValue();
		var store = this.grid.store;
		
		// get fields to search array
		var fields = [];
		this.menu.items.each(function(item) {
			if(item.checked) {
				var col_name = item.dataIndex;
				if(this.fieldNameMap[col_name]) { col_name = this.fieldNameMap[col_name]; }
				fields.push(col_name);
			}
		},this);

		// add fields and query to baseParams of store
		delete(store.baseParams[this.paramNames.fields]);
		delete(store.baseParams[this.paramNames.query]);
		delete(store.baseParams[this.paramNames.quicksearch_mode]);
		if (store.lastOptions && store.lastOptions.params) {
			delete(store.lastOptions.params[this.paramNames.fields]);
			delete(store.lastOptions.params[this.paramNames.query]);
			delete(store.lastOptions.params[this.paramNames.quicksearch_mode]);
		}
		//if(fields.length && !this.field.disabled) {
			store.baseParams[this.paramNames.fields] = Ext.encode(fields);
			store.baseParams[this.paramNames.query] = val;
			store.baseParams[this.paramNames.quicksearch_mode] = this.grid.quicksearch_mode;
		//}
	}


	// eo function onTriggerSearch
	// }}}
	// {{{
	/**
	 * @param {Boolean} true to disable search (TwinTriggerField), false to enable
	 */
	,setDisabled:function() {
		this.field.setDisabled.apply(this.field, arguments);
	} // eo function setDisabled
	// }}}
	// {{{
	/**
	 * Enable search (TwinTriggerField)
	 */
	,enable:function() {
		this.setDisabled(false);
	} // eo function enable
	// }}}
	// {{{
	/**
	 * Disable search (TwinTriggerField)
	 */
	,disable:function() {
		this.setDisabled(true);
	} // eo function disable
	// }}}
	// {{{
	/**
	 * (re)configures the plugin, creates menu items from column model
	 * @private 
	 */
	,reconfigure:function() {
		var store = this.grid.getStore()
	
		// NEW: Try to load checkIndex list from a property in the grid store
		// (added for saved state integration, 2012-09-18 by HV)
		this.checkIndexes = store.quickSearchCheckIndexes || this.checkIndexes;
		
		// Added for saved state integration 2012-12-16 by HV:
		this.grid.quicksearch_mode = store.quicksearch_mode;
		
		// {{{
		// remove old items
		var menu = this.menu;
		menu.removeAll();
		
		
		// add Select All item plus separator
		if(this.showSelectAll && 'radio' !== this.menuStyle) {
			menu.add(new Ext.menu.CheckItem({
				 text:this.selectAllText
				,checked:!(this.checkIndexes instanceof Array)
				,hideOnClick:false
				,handler:function(item) {
					var checked = ! item.checked;
					item.parentMenu.items.each(function(i) {
						if(item !== i && i.setChecked && !i.disabled) {
							i.setChecked(checked);
						}
					});
				}
			}),'-');
		}

		// }}}
		// {{{
		// add new items
		//var cm = this.grid.colModel;
		var columns = this.getQuickSearchColumns();
		
		var group = undefined;
		if('radio' === this.menuStyle) {
			group = 'g' + (new Date).getTime();	
		}
		//Ext.each(cm.config, function(config) {
		Ext.each(columns, function(config) {
			
			var disable = false;
			Ext.each(this.disableIndexes, function(item) {
				disable = disable ? disable : item === config.dataIndex;
			});
			if(!disable) {
				menu.add(new Ext.menu.CheckItem({
					 text:config.header
					,hideOnClick:false
					,group:group
					,checked:'all' === this.checkIndexes
					,dataIndex:config.dataIndex
				}));
			}
		}, this);
		// }}}
		// {{{
		// check items
		if(this.checkIndexes instanceof Array) {
			Ext.each(this.checkIndexes, function(di) {
				var item = menu.items.find(function(itm) {
					return itm.dataIndex === di;
				});
				if(item) {
					item.setChecked(true, true);
				}
			}, this);
		}
		// }}}
		// {{{
		// disable items
		if(this.readonlyIndexes instanceof Array) {
			Ext.each(this.readonlyIndexes, function(di) {
				var item = menu.items.find(function(itm) {
					return itm.dataIndex === di;
				});
				if(item) {
					item.disable();
				}
			}, this);
		}
		// }}}
		
		this.persistCheckIndexes();
	}, 
	
	getQuickSearchColumns: function() {
		if(!this.QuickSearchColumns) {
			var columns = this.grid.initialConfig.columns;
			var quick_search_columns = [];
			Ext.each(columns, function(config) {
				var disable = false;
				if(config.header && config.dataIndex && !config.no_quick_search) {
					quick_search_columns.push(config);
				}
			},this);
			this.QuickSearchColumns = quick_search_columns;
		}
		return this.QuickSearchColumns;
	},
	
	applySearchMode: function(){
		var item;
		this.modeMenu.items.each(function(i) {
			if(i.checked) { item = i; }
		},this);
		if(!item || !item.header || !item.mode) {
			// Fallback/default: should never get here:
			item = { header: 'Quick Search', mode: 'like' };
		}
		
		this.searchText = item.header;
		this.grid.quicksearch_mode = item.mode;
		this.grid.store.quicksearch_mode = item.mode;
	},
	
	persistCheckIndexes: function(){
		
		this.applySearchMode();
		
		var indexes = [];
		var headers = [];
		var all = true;
		this.menu.items.each(function(item) {
			if(item.checked && item.dataIndex) {
				headers.push(item.text);
				indexes.push(item.dataIndex);
			}
			else {
				if(item.dataIndex) { all = false; }
			}
		},this);
		
		// -- New: Persist checkIndexes for saved state integration:
		this.grid.store.quickSearchCheckIndexes = indexes;
		// --
		
		var sup = all ? 'all' : indexes.length;
		
		var btnText = this.searchText + 
			'<span class="superscript-green" style="font-weight:bold;padding-left:1px;">' + sup + '</span>';
			
		if(indexes.length == 1) {
			var header = headers[0] || indexes[0];
			btnText = this.searchText + ' | ' + header; 
		}
		
		this.button.setText(btnText);
		
		//
		this.field.setDisabled(!indexes.length);
		
	}

}); 



/*
 Ext.ux.RapidApp.Plugin.GridHmenuColumnsToggle
 2011-06-08 by HV

 Plugin for Ext.grid.GridPanel that converts the 'Columns' hmenu submenu item
 into a Ext.ux.RapidApp.menu.ToggleSubmenuItem (instead of Ext.menu.Item)

 See the Ext.ux.RapidApp.menu.ToggleSubmenuItem class for more details.
*/
Ext.ux.RapidApp.Plugin.GridHmenuColumnsToggle = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		cmp.on('afterrender',this.onAfterrender,this);
	},
	
	onAfterrender: function() {
		
		var hmenu = this.cmp.view.hmenu;
		if(!hmenu) { return; }
		
		var colsItem = hmenu.getComponent('columns');
		if (!colsItem) { return; }
		colsItem.hide();
		
		hmenu.add(Ext.apply(Ext.apply({},colsItem.initialConfig),{
			xtype: 'menutoggleitem',
			itemId:'columns-new'
		}));
	}
});
Ext.preg('grid-hmenu-columns-toggle',Ext.ux.RapidApp.Plugin.GridHmenuColumnsToggle);


/*
 Ext.ux.RapidApp.Plugin.GridHmenuClearSort
 2012-07-21 by HV

 Plugin for Ext.grid.GridPanel that adds "Clear Current Sort" to Hmenu
*/
Ext.ux.RapidApp.Plugin.GridHmenuClearSort = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		cmp.on('afterrender',this.onAfterrender,this);
	},
	
	getClearSortButton: function(){
		return {
			text: 'Clear Current Sort',
			itemId: 'clear-sort',
			iconCls: 'ra-icon-remove-sort',
			handler: function() {
				this.cmp.store.setDefaultSort(null);
				this.cmp.store.reload();
			},
			scope: this
		};
	},
	
	beforeMenuShow: function(hmenu){
		var clearSortItem = hmenu.getComponent('clear-sort');
		if (!clearSortItem) { return; }
		var store = this.cmp.getStore();
		var curSort = store ? store.getSortState() : null;
		clearSortItem.setVisible(curSort);
	},
	
	onAfterrender: function() {
		var hmenu = this.cmp.view.hmenu;
		if(!hmenu) { return; }
		hmenu.insert(2,this.getClearSortButton());
		hmenu.on('beforeshow',this.beforeMenuShow,this);
	}
});
Ext.preg('grid-hmenu-clear-sort',Ext.ux.RapidApp.Plugin.GridHmenuClearSort);



// For use with Fields, returns empty strings as null
Ext.ux.RapidApp.Plugin.EmptyToNull = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		var nativeGetValue = cmp.getValue;
		cmp.getValue = function() {
			var value = nativeGetValue.call(cmp);
			if (value == '') { return null; }
			return value;
		}
	}
});
Ext.preg('emptytonull',Ext.ux.RapidApp.Plugin.EmptyToNull);

// For use with Fields, returns nulls as empty string
Ext.ux.RapidApp.Plugin.NullToEmpty = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		var nativeGetValue = cmp.getValue;
		cmp.getValue = function() {
			var value = nativeGetValue.call(cmp);
			if (value == null) { return ''; }
			return value;
		}
	}
});
Ext.preg('nulltoempty',Ext.ux.RapidApp.Plugin.NullToEmpty);

// For use with Fields, returns false/true as 0/1
Ext.ux.RapidApp.Plugin.BoolToInt = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		var nativeGetValue = cmp.getValue;
		cmp.getValue = function() {
			var value = nativeGetValue.call(cmp);
			if (value == false) { return 0; }
			if (value == true) { return 1; }
			return value;
		}
	}
});
Ext.preg('booltoint',Ext.ux.RapidApp.Plugin.BoolToInt);


// Plugin adds '[+]' to panel title when collapsed if titleCollapse is on
Ext.ux.RapidApp.Plugin.TitleCollapsePlus = Ext.extend(Ext.util.Observable,{
	
	addedString: '',
	
	init: function(cmp) {
		this.cmp = cmp;
		
		if (this.cmp.titleCollapse && this.cmp.title) {
			this.cmp.on('beforecollapse',function(){
				this.updateCollapseTitle.call(this,'collapse');
			},this);
			this.cmp.on('beforeexpand',function(){
				this.updateCollapseTitle.call(this,'expand');
			},this);
			this.cmp.on('afterrender',this.updateCollapseTitle,this);
		}
	},
	
	updateCollapseTitle: function(opt) {
		if (!this.cmp.titleCollapse || !this.cmp.title) { return; }
		
		if(opt == 'collapse') { return this.setTitlePlus(true); }
		if(opt == 'expand') { return this.setTitlePlus(false); }
		
		if(this.cmp.collapsed) {
			this.setTitlePlus(true);
		}
		else {
			this.setTitlePlus(false);
		}
	},
	
	getTitle: function() {
		return this.cmp.title.replace(this.addedString,'');
	},
	
	setTitlePlus: function(bool) {
		var title = this.getTitle();
		if(bool) {
			this.addedString = 
				'&nbsp;<span style="font-weight:lighter;font-family:monospace;color:gray;">' + 
					'&#91;&#43;&#93;' +
				'</span>';
			return this.cmp.setTitle(title + this.addedString);
		}
		this.addedString = '';
		return this.cmp.setTitle(title);
	}
});
Ext.preg('titlecollapseplus',Ext.ux.RapidApp.Plugin.TitleCollapsePlus);



// Automatically expands label width according to the longest fieldLabel
// in a form panel's items, if needed
Ext.ux.RapidApp.Plugin.DynamicFormLabelWidth = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		if(!cmp.labelWidth) { return; } // <-- a labelWidth must be set for plugin to be active
		var longField = this.getLongestField();
		if(!longField){ return; }
		
		Ext.applyIf(longField,this.cmp.defaults || {});
		longField.hidden = true;
		
		var label = new Ext.form.Label({
			renderTo: document.body,
			cls: 'x-hide-offsets',
			text: 'I',
			hidden: true,
			style: longField.labelStyle || ''
		});
		label.show();
		var metrics = Ext.util.TextMetrics.createInstance(label.getEl());
		var calcWidth = metrics.getWidth(longField.fieldLabel) + 10;
		label.destroy();
		
		if(calcWidth > cmp.labelWidth) {
			cmp.labelWidth = calcWidth;
		}
	},
	
	getLongestField: function() {
		var longField = null;
		var longLen = 0;
		this.cmp.items.each(function(item){
			if(!Ext.isString(item.fieldLabel)) { return; }
			var curLen = item.fieldLabel.length;
			if(curLen > longLen) {
				longLen = curLen;
				longField = item;
			}
		},this);
		if(!longField){ return null; }
		return Ext.apply({},longField); //<-- return a copy instead of original
	}
});
Ext.preg('dynamic-label-width',Ext.ux.RapidApp.Plugin.DynamicFormLabelWidth);



// This is stupid, but needed to support an auto height Grid with autoScroll
// to be able to scroll left/right:
Ext.ux.RapidApp.Plugin.gridAutoHeight = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		cmp.on('resize',this.setAutoHeight,this);
		cmp.on('viewready',this.onViewReady,this);
	},
	
	onViewReady: function() {
		this.setAutoHeight.call(this);
		var view = this.cmp.getView();
		view.on('refresh',this.setAutoHeight,this);
	},
	
	setAutoHeight: function() {
		var grid = this.cmp;
		var el1 = grid.getEl().child('div.x-grid3');
		if(el1) { el1.setHeight('auto'); }
		var el2 = grid.getEl().child('div.x-grid3-scroller');
		if(el2) { el2.setHeight('auto'); }
	}
});
Ext.preg('grid-autoheight',Ext.ux.RapidApp.Plugin.gridAutoHeight);




// Plugin for ManagedIframe
// Sets the height based on the iframe height after it's loaded
Ext.ux.RapidApp.Plugin.autoHeightIframe = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		this.cmp.on('domready',this.onDocumentLoaded,this);
	},
	
	onDocumentLoaded: function(el) {
		this.setHeight.defer(100,this,[el]);
	},
	
	setHeight:function(el) {
		var height = el.dom.contentDocument.activeElement.scrollHeight;
		this.cmp.setHeight(height + 35);
	}
});
Ext.preg('iframe-autoheight',Ext.ux.RapidApp.Plugin.autoHeightIframe);



Ext.ux.RapidApp.Plugin.ReadOnlyField = Ext.extend(Ext.util.Observable,{
	
	styles: {
		'background-color': 'transparent',
		'border-color': 'transparent',
		'background-image': 'none',
		
		// the normal text field has padding-top: 2px which makes the text sit towards
		// the bottom of the field. We set top and bot here to move one of the px to the
		// bottom so the text will be vertically centered but take up the same vertical
		// size as a normal text field:
		'padding-top': '1px',
		'padding-bottom': '1px'
	},
	
	getStyleString: function() {
		var str = '';
		Ext.iterate(this.styles,function(k,v) {
			str += k + ':' + v + ';';
		},this);
		return str;
	},
	
	init: function(cmp) {
		this.cmp = cmp;
		
		cmp.style = cmp.style || '';
		
		if(Ext.isObject(cmp.style)) {
			Ext.apply(cmp.style,this.styles);
		}
		else {
			cmp.style += this.getStyleString();
		}
		
		cmp.readOnly = true;
		cmp.allowBlank = true;
	}
	
});
Ext.preg('read-only-field',Ext.ux.RapidApp.Plugin.ReadOnlyField);


Ext.ux.RapidApp.Plugin.AppGridSummary = Ext.extend(Ext.ux.grid.GridSummary, {
	
	showMenu : true,
	menuSummaryText : 'Summary Function',
	headerIcoDomCfg: {
		tag: 'div',
		cls: 'ra-icon-function-small',
		style: 'float:left;width:10px;height:12px;'
	},
	allow_cust_funcs: true,
	
	// These functions will use the same renderer for the normal column, all others
	// will render raw data:
	orig_renderer_funcs: ['min','max','sum','avg'],
	
	init: function(grid) {
		Ext.ux.RapidApp.Plugin.AppGridSummary.superclass.init.apply(this,arguments);
		
		grid.appgridsummary = this;
		if(typeof grid.allow_custom_summary_functions !== "undefined") {
			this.allow_cust_funcs = grid.allow_custom_summary_functions;
		}
		
		this.orig_renderer_map = {};
		Ext.each(this.orig_renderer_funcs,function(f){ 
			this.orig_renderer_map[f.toUpperCase()] = true; 
		},this);
		
		this.store = grid.getStore();
		
		if(grid.init_state && grid.init_state.column_summaries) {
			this.store.column_summaries = grid.init_state.column_summaries;
		}
		
		/*
		var plugin = this, store = this.store;
		grid.applyColumnSummaryParams = function(){
			if(store.column_summaries) {
				var params = { 'column_summaries': plugin.getEncodedParamVal()  };
				Ext.apply(store.baseParams,params);
				// Set lastOptions as well so reload() gets the changes:
				Ext.apply(store.lastOptions.params,params);
			}
			return true;
		}
		this.store.on('beforeload',grid.applyColumnSummaryParams);
		*/
		
		// -- Make sure these exist to prevent possible undef errors later on:
		//this.store.baseParams = this.store.baseParams || {};
		//this.store.lastOptions = this.store.lastOptions || {};
		//this.store.lastOptions.params = this.store.lastOptions.params || {};
		// --
		
		this.store.on('beforeload',function(store,options) {
			if(store.baseParams) { 
				delete store.baseParams.column_summaries; 
			}
			if(store.lastOptions && store.lastOptions.params) { 
				delete store.lastOptions.params.column_summaries; 
			}
			if(store.column_summaries) {
				var column_summaries = this.getEncodedParamVal();
				
				// Forcefully set both baseParams and lastOptions so make sure
				// no param caching is happening in the Ext.data.Store
				store.baseParams.column_summaries = column_summaries;
				store.lastOptions.params.column_summaries = column_summaries;
				
				// this is required for very first load to see changes 
				// (not sure why this is needed beyond the above lines)
				Ext.apply(options.params, {column_summaries: column_summaries});
			}
			return true;
		},this);
		

		grid.on('reconfigure',this.updateColumnHeadings,this);
		
		// NEW: needed when using new "Open New Tab" feature to hook into toggling
		// tabs (without this the old tab would have the heading icons stripped
		// TODO: look into cleaning up the mechanism that sets the heading icons
		if(grid.ownerCt) {
			grid.ownerCt.on('show',this.updateColumnHeadings,this);
		}
		
		this.cm.on('hiddenchange',this.autoToggle,this);
		
		if (grid.rendered) {
			this.onRender();
		} else {
			grid.on({
				scope: this,
				single: true,
				afterrender: this.onRender
			});
		}
	},
	
	getEncodedParamVal: function() {
		var column_summaries = this.store.column_summaries || {};
		var data = {};
				
		Ext.iterate(column_summaries,function(k,v){
			if(v['function']) { data[k] = v['function']; }
		},this);
		return Ext.encode(data);
	},
	
	onRender: function() {
		// Always start with summary line hidden:
		this.autoToggle();
		
		if(this.getSummaryCols()) {
			this.grid.getView().on('refresh', this.onRefresh, this);
			this.createMenu();
			this.updateColumnHeadings();
		}
	},
	
	onRefresh : function () {
		this.updateColumnHeadings();
	},
	
	getSummaryCols: function() {
		if(!this.summaryCols) {
			var summaryCols = {};
			var count = 0;
			var columns = this.grid.initialConfig.columns;
			Ext.each(columns,function(column){
				
				if(column.no_summary) { return; }
				
				if(this.allow_cust_funcs){
					column.summary_functions = column.summary_functions || [];
				}
				
				if(Ext.isArray(column.summary_functions)) {
					summaryCols[column.name] = column.summary_functions;
					count++
				}
			},this);
			this.summaryCols = summaryCols;
			if(count == 0) { this.summaryCols = null; }
		}
		return this.summaryCols;
	},
	
	getComboField: function() {
		if(!this.comboField) {
			var cnf = {
				id: this.grid.id + '-summary-combo-field',
				storedata: [],
				editable: false,
				forceSelection: true,
				name: 'combo',
				fieldLabel: 'Select Function',
				hideLabel: true,
				xtype: 'static-combo',
				width: 200,
				listeners:{
					select: {
						scope: this,
						fn: function(combo,record,index) {
							
							var func = record.data.valueField,
								title = record.data.displayField;
							
							var currentVal = combo.getValue();
							
							var setVal = func, setTitle = title;
							if(func == '(None)' || func == 'Custom Function:') { 
								// Don't clear the custom func if its already set
								//if(currentVal == func && func == 'Custom Function:') {  
								//} else {
									setVal = null;
									setTitle = null;
								//}
							}
							
							var field = this.getFunctionsField(), 
								tfield = this.getTitleField();
							
							field.setValue(setVal);
							tfield.setValue(setTitle);
							
							if (func == 'Custom Function:') {
								field.setVisible(true);
								tfield.setVisible(true);
							}
							else {
								this.applySelection();
							}
						}
					},
					beforequery: function(qe){
						delete qe.combo.lastQuery;
					},
					expand: {
						scope: this,
						fn: function() { this.setPreventMenuHide(true); }
					},
					collapse: {
						scope: this,
						fn: function() { this.setPreventMenuHide.defer(300,this,[false]); }
					}
				}
			};
			this.comboField = Ext.ComponentMgr.create(cnf,'static-combo');
		}
		return this.comboField;
	},
	
	menuHideAllowed: function() {
		var bool = this.preventMenuHide ? true : false;
		return bool;
	},
	
	setPreventMenuHide: function(bool) {
		this.preventMenuHide = bool ? false : true;
	},
	
	getFunctionsField: function() {
		if(!this.functionsField) {
			var cnf = {
				id: this.grid.id + '-summary-funcs-field',
				name: 'function',
				fieldLabel: 'Custom Function',
				emptyText: '(Enter Function Code)',
				emptyClass: 'field-empty-text',
				fieldClass: 'blue-text-code',
				//style: { 'font-family': 'Courier', color: 'blue' },
				hideLabel: true,
				xtype: 'textfield',
				width: 200,
				enableKeyEvents:true,
				listeners:{
					keyup:{
						scope: this,
						buffer: 150,
						fn: function(field, e) {
							if (Ext.EventObject.ENTER == e.getKey()){
								this.applySelection();
							}
						}
					}
				}
			};
			this.functionsField = Ext.ComponentMgr.create(cnf,'textfield');
		}
		return this.functionsField;
	},
	
	getTitleField: function() {
		if(!this.titleField) {
			var cnf = {
				id: this.grid.id + '-title-field',
				name: 'title',
				fieldLabel: 'Title',
				emptyText: '(Optional)',
				emptyClass: 'field-empty-text',
				//hideLabel: true,
				xtype: 'textfield',
				//width: 170,
				anchor: "100%",
				enableKeyEvents:true,
				listeners:{
					keyup:{
						scope: this,
						buffer: 150,
						fn: function(field, e) {
							if (Ext.EventObject.ENTER == e.getKey()){
								this.applySelection();
							}
						}
					}
				}
			};
			this.titleField = Ext.ComponentMgr.create(cnf,'textfield');
		}
		return this.titleField;
	},
	
	createMenu : function () {
		var view = this.grid.getView(),
			hmenu = view.hmenu;

		if (this.showMenu && hmenu) {
			
			this.sep = hmenu.addSeparator();
			this.summaryMenu = new Ext.menu.Menu({
				id: this.grid.id + '-summary-menu',
				layout: 'form',
				showSeparator: false,
				labelAlign: 'right',
				labelWidth: 30,
				items: [
					this.getComboField(),
					this.getFunctionsField(),
					this.getTitleField()
				]
			});
			
			this.clearAllItem = hmenu.add({
				iconCls: 'ra-icon-function-clear',
				itemId: 'clear-all',
				text: 'Clear All Summaries',
				handler: this.clearAllSummaries,
				scope: this
			});
			
			this.menu = hmenu.add({
				hideOnClick: false,
				iconCls: 'ra-icon-checkbox-no',
				itemId: 'summary',
				text: this.menuSummaryText,
				menu: this.summaryMenu
			});

			hmenu.on('beforeshow', this.onMenu, this);
			hmenu.on('beforehide',this.menuHideAllowed,this);
			this.summaryMenu.on('beforehide',this.menuHideAllowed,this);
		}
	},
	
	applySelection: function() {
		var colname = this.getActiveColName(),
			field = this.getFunctionsField(),
			tfield = this.getTitleField();
		
		if(!colname || !field) { return false; }
		
		this.setPreventMenuHide(false);
		
		if(field.validate()) {
			var func_str = field.getValue();
			var title = tfield.getValue();
			if(!title || title == '') { 
				title = this.getColSummaryFuncTitle(colname,func_str) || func_str; 
			}
			this.grid.view.hmenu.hide();
			this.setColSummary(colname,func_str,title);
			return true;
		}
		else {
			field.markInvalid();
			return false;
		}
	},
	
	getColSummaryFuncs: function(colname) {
		var summaryCols = this.getSummaryCols() || {};
		return summaryCols[colname] || [];
	},
	
	getColSummaryFuncTitle: function(colname,f) {
		var funcs = this.getColSummaryFuncs(colname);
		var title = null;
		Ext.each(funcs,function(func) {
			if(func['function'] == f) { title = func.title; }
		},this);
		return title;
	},
	
	loadSelection: function() {
		var colname = this.getActiveColName(),
			field = this.getFunctionsField(),
			combo = this.getComboField(),
			tfield = this.getTitleField(),
			menu = this.menu;
		
		if(!field) { return false; }
		
		var summary_data = this.getColSummary(colname);
		
		var funcs = this.getColSummaryFuncs(colname);
		
		var storedata = [];
		storedata.push([
			'(None)',
			'(None)',
			'field-empty-text',
			'padding-bottom:6px;'
		]);
		
		var seen_funcs = {};
		Ext.each(funcs,function(func){
			if(!func['function'] || seen_funcs[func['function']]) { return; }
			seen_funcs[func['function']] = true;
			func.title = func.title || func['function'];
			storedata.push([func['function'],func.title,'x-no-class','']);
		},this);
		
		storedata.push([
			'Custom Function:',
			'Custom Function:',
			'blue-text-code-bold',
			'padding-top:6px;font-size:1.15em;'
		]);
		
		combo.getStore().loadData(storedata);
		
		if(summary_data) {
			var val = summary_data['function'], title = summary_data['title'];
			if(val && val !== '') {
				//menu.setIconClass('ra-icon-checkbox-yes');
				menu.setIconClass('ra-icon-function');
				field.setValue(val);
				tfield.setValue(title);
				if(seen_funcs[val]) {
					combo.setValue(val);
					field.setVisible(false);
					tfield.setVisible(false);
				}
				else {
					combo.setValue('Custom Function:');
					field.setVisible(true);
					tfield.setVisible(true);
				}
			}
		}
		else {
			combo.setValue('(None)');
			menu.setIconClass('ra-icon-checkbox-no');
			field.setVisible(false);
			tfield.setVisible(false);
			tfield.setValue(null);
			return field.setValue(null);
		}
	},
	
	updateColumnHeadings: function () {
    this.hdIcos = this.hdIcos || {};
		var view = this.grid.getView(),
			hds, i, len, summary_data;
		if (view.mainHd) {

			hds = view.mainHd.select('td');
			for (i = 0, len = view.cm.config.length; i < len; i++) {
				var itm = hds.item(i);
				
				if(this.hdIcos[i]) { this.hdIcos[i].remove(); delete this.hdIcos[i]; }
				summary_data = this.getColSummary(view.cm.config[i].name);
				if (summary_data) {
					this.hdIcos[i] = itm.child('div').insertFirst(this.headerIcoDomCfg);
				}
			}
		}
	},
	
	getActiveColName: function() {
		var view = this.grid.getView();
		if (!view || view.hdCtxIndex === undefined) {
			return null;
		}
		var col = view.cm.config[view.hdCtxIndex];
		if(!col){ return null; }
		return col.name;
	},
	
	getColFuncList : function () {
		var colname = this.getActiveColName(),
			summaryCols = this.getSummaryCols();
		
		if (!summaryCols || !colname) { return null; }
		return summaryCols[colname];
	},
	
	onMenu: function(){
		this.setPreventMenuHide(false);
		this.summaryMenu.hide();
		var funcs = this.getColFuncList();
		if(funcs) {
			this.loadSelection();
		}
		this.menu.setVisible(funcs !== undefined);
		this.sep.setVisible(funcs !== undefined);
		this.clearAllItem.setVisible(this.hasActiveSummaries());
	},
	
	autoToggle: function() {
		this.toggleSummary(this.hasActiveSummaries() ? true : false);
	},
	
	hasActiveSummaries: function() {
		var column_summaries = this.store.column_summaries;
		if(!column_summaries) { return false; }
		var cm = this.grid.getColumnModel();
		for(i in cm.config) {
			var c = cm.config[i];
			if(c && column_summaries[c.name] && !c.hidden) {
				return true;
			}
		}
		return false;
	},
	
	getColSummary: function(colname) {
		var summary, column_summaries = this.store.column_summaries;
		if(!colname || !column_summaries || !column_summaries[colname]){
			summary = null;
		}
		else {
			summary = column_summaries[colname];
		}
		return summary;
	},
	
	setColSummary: function(colname,func_str,title) {
		title = title || func_str;
		
		var cur = this.getColSummary(colname);
		if(cur && cur['function'] == func_str && cur['title'] == title) {
			return; //<-- nothing changed
		}
		
		if(!func_str || func_str == '') {
			if(!cur) { return; }
			return this.removeColSummary(colname);
		}
		
		var store = this.store;
		if(!store.column_summaries) { store.column_summaries = {}; }
		
		store.column_summaries[colname] = {
			'function': func_str,
			'title': title
		};
		
		this.onSummaryDataChange();
	},
	
	removeColSummary: function(colname) {
		var column_summaries = this.store.column_summaries;
		if(!colname || !column_summaries || !column_summaries[colname]){
			return;
		}
		delete column_summaries[colname];
		this.onSummaryDataChange();
	},
	
	getSummaryColumnList: function() {
		var column_summaries = this.store.column_summaries;
		if(!column_summaries) { return []; }
		var columns = [];
		Ext.iterate(column_summaries,function(k,v){
			columns.push(k);
		},this);
		return columns;
	},
	
	onSummaryDataChange: function() {
		var store = this.store;
		var columns = this.getSummaryColumnList();
		if(columns.length == 0) {
			delete store.column_summaries;
		};
		this.updateColumnHeadings();
		store.reload();
		this.autoToggle();
	},
	
	// override Ext.ux.grid.GridSummary.calculate:
	calculate: function() {
		var jsonData = this.store.reader.jsonData;
		return (jsonData && jsonData.column_summaries) ? jsonData.column_summaries : {};
	},
	
	renderSummary : function(o, cs, cm) {
		cs = cs || this.view.getColumnData();
		var cfg = cm.config,
			buf = [],
			last = cs.length - 1;
		
		for (var i = 0, len = cs.length; i < len; i++) {
			var c = cs[i], cf = cfg[i], p = {};
				
			p.id = c.id;
			p.style = c.style;
			p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
			
			var summary = this.getColSummary(c.name) || {};
			var func = summary['function'];
			if(func) { func = func.toUpperCase(); }

			if (o.data[c.name]) {
				p.value = o.data[c.name];
				if(this.orig_renderer_map[func]) {
					p.value = c.renderer(o.data[c.name], p, o);
				}
				
				if(o.data[c.name] == 'BadFunc!' || o.data[c.name] == 'FuncError!') {
					p.value = '<span style="font-size:.9em;font-family:Courier;color:red;">' +
						o.data[c.name] +
					'</span>';
				}
				
				var title = summary.title;
					
				if(title && title !== '') {
					var html = '<div style="padding-top:6px;padding-bottom:5px;font-size:.9em;color:darkgray">' + 
						Ext.DomHelper.markup(this.headerIcoDomCfg) + 
						'<div>' + title + '</div></div>' +
					'<div>' + p.value + '</div>';
					
					p.value = html;
				}
				
			} else {
				 p.value = '';
			}
			if (p.value === undefined || p.value === "") {
				 p.value = "&#160;";
			}

			
			buf[buf.length] = this.cellTpl.apply(p);
		}
		
		var tstyle = 
			'width:' + this.view.getTotalWidth() + ';' +
			'height:44px;';

		return this.rowTpl.apply({
			tstyle: tstyle,
			cells: buf.join('')
		});
	},
	
	clearAllSummaries: function() {
		if(this.grid.store.column_summaries) {
			delete this.grid.store.column_summaries;
		}
		this.onSummaryDataChange();
	}
	
});
Ext.preg('appgrid-summary',Ext.ux.RapidApp.Plugin.AppGridSummary);


// ------- http://extjs.com/forum/showthread.php?p=97676#post97676
// Note that the "shallow" options below were added by HV, and only consider the headers...
// this was done because this can take a long time with big grids
Ext.override(Ext.CompositeElementLite, {
	getTextWidth: function() {
		var i, e, els = this.elements, result = 0;
		for(i = 0; e = Ext.get(els[i]); i++) {
			result = Math.max(result, e.getTextWidth.apply(e, arguments));
		}
		return result;
	 },
	 getTextWidthShallow: function() {
		var i, e, els = this.elements, result = 0;
		for(i = 0; e = Ext.get(els[i]); i++) {
			result = Math.max(result, e.getTextWidth.apply(e, arguments));
			return result;
		}
		return result;
	 }
});
// -------

Ext.ux.RapidApp.Plugin.AppGridAutoColWidth = Ext.extend(Ext.util.Observable,{
	
	init: function(grid) {
		grid.on('render',this.onRender,grid);
		grid.autosize_maxwidth = grid.autosize_maxwidth || 500;
		
		Ext.apply(grid,{
			// ------- http://extjs.com/forum/showthread.php?p=97676#post97676
			autoSizeColumnHeaders: function(){
				return this.doAutoSizeColumns(true);
			},
			autoSizeColumns: function(){
				// Not using Ext.LoadMask because it doesn't work right; I think because
				// it listens to events that the resize causes to fire. All it really does
				// is call El.mask and El.unmask anyway...
				var El = this.getEl();
				El.mask("Autosizing Columns...",'x-mask-loading');

				this.doAutoSizeColumns.defer(10,this,[false,El]);
			},
			doAutoSizeColumns: function(shallow,El) {
				var cm = this.colModel;
				if(!cm) { return; }
				
				// Shrink all columns to a floor value first because we can only "size-up"
				if(!shallow) { this.setAllColumnsSize(10); }
				
				cm.suspendEvents();
				var col_count = cm.getColumnCount();
				for (var i = 0; i < col_count; i++) {
					this.autoSizeColumn(i,shallow);
				}
				cm.resumeEvents();
				this.view.refresh(true);
				this.store.removeListener('load',this.autoSizeColumns,this);
				this.store.removeListener('load',this.autoSizeColumnHeaders,this);
				if(El) {
					El.unmask();
				}
			},
			autoSizeColumn: function(c,shallow) {
				var cm = this.colModel;
				var colid = cm.getColumnId(c);
				var column = cm.getColumnById(colid);
				
				// Skip hidden columns unless autoSizeHidden is true:
				if(!this.autosize_hidden && column.hidden) {
					return;
				}
				
				var col = this.view.el.select("td.x-grid3-td-" + colid + " div:first-child");
				if (col) {

					var add = 6;
					var w = add;
					if(shallow) {
						w += col.getTextWidthShallow();
					}
					else {
						w += col.getTextWidth();
					}
					w += Ext.get(col.elements[0]).getFrameWidth('lr');
					
					w = this.autosize_maxwidth < w ? this.autosize_maxwidth : w;

					//if (w > this.autosize_maxwidth) { w = this.autosize_maxwidth; }
					if (column.width && w < column.width) { w = column.width; }

					cm.setColumnWidth(c, w);
					return w;
				}
			},
			setAllColumnsSize: function(size) {
				var cm = this.colModel;
				cm.suspendEvents();
				var col_count = cm.getColumnCount();
				for (var i = 0; i < col_count; i++) {
          var colid = cm.getColumnId(i);
          var column = cm.getColumnById(colid);
          // Skip hidden columns unless autoSizeHidden is true:
          if(!this.autosize_hidden && column.hidden) {
            continue;
          }
					cm.setColumnWidth(i, size);
				}
				cm.resumeEvents();
			}
			// ------------------------
		});
	},
	
	onRender: function() {
		
		if(typeof this.use_autosize_columns != 'undefined' && !this.use_autosize_columns) {
			// if use_autosize_columns is defined and set to false, abort setup even if
			// other params are set below:
			return;
		}
		
		if(typeof this.auto_autosize_columns != 'undefined' && !this.auto_autosize_columns) {
			// if auto_autosize_columns is defined and set to false:
			this.auto_autosize_columns_deep = false;
		}
			
		if(this.auto_autosize_columns_deep) {
			// This can be slow, but still provided as an option 'auto_autosize_columns_deep'
			this.store.on('load',this.autoSizeColumns,this);
		}
		else if (this.auto_autosize_columns){
			// this only does headers, faster:
			this.store.on('load',this.autoSizeColumnHeaders,this); 
		}
			
		if(this.use_autosize_columns) {
			//var menu = this.getOptionsMenu();
			var hmenu = this.view.hmenu;
			if(!hmenu) { return; }

			var index = 0;
			var colsItem = hmenu.getComponent('columns');
			if(colsItem) {
				index = hmenu.items.indexOf(colsItem);
			}
			
			hmenu.insert(index,{
				text: "AutoSize Columns",
				iconCls: 'ra-icon-left-right',
				handler:this.autoSizeColumns, //<-- this does the full autosize which could be slow
				scope: this
			});
		}
	}
	
});
Ext.preg('appgrid-auto-colwidth',Ext.ux.RapidApp.Plugin.AppGridAutoColWidth);



// Base plugin class for plugins that add special items to the col menu:
Ext.ux.RapidApp.Plugin.AppGridColMenuPlug = Ext.extend(Ext.util.Observable,{
	
	maxSeparatorPosition: 4,
	
	init: function(grid) {
		this.grid = grid;
		grid.on('render',this.onRender,this);
	},
	
	getColItem: Ext.emptyFn,
	
	getColCount: function() {
		return this.view.colMenu.items.getCount();
	},
	
	getExistingSeparatorIndex: function() {
		var items = this.colMenu.items.items;
		for (ndx in items) {
			if(ndx > this.maxSeparatorPosition) { return -1; }
			if(this.isSeparator(items[ndx])) { return ndx; }
		}
		return -1;
	},
	
	isSeparator: function(item) {
		if((Ext.isObject(item) && item.itemCls == 'x-menu-sep') || item == '-') {
			return true;
		}
		return false;
	},
	
	getInsertPosition: function() {
		var pos = this.getExistingSeparatorIndex();
		if(pos == -1) { 
			this.colMenu.insert(0,'-');
			return 0;
		}
		return pos;
	},
	
	onRender: function() {
		this.view = this.grid.getView();
		this.cm = this.grid.getColumnModel();
		this.colMenu = this.view.colMenu;
		if(!this.colMenu) { return; }
		
		this.colMenu.on('beforeshow',function(){
			
			//Disable the menu keyNav to allow arrow keys to work in fields within the menu:
			if(this.colMenu.keyNav){ this.colMenu.keyNav.disable(); }
			
			var colItem = this.getColItem();
			if(!colItem) { return; }
			var pos = this.getInsertPosition();
			this.colMenu.insert(pos,colItem);
		},this);
	}
});



// Adds a special "Toggle All" checkbox to the top of the grid Columns menu:
Ext.ux.RapidApp.Plugin.AppGridToggleAllCols = Ext.extend(Ext.ux.RapidApp.Plugin.AppGridColMenuPlug,{
	
	getColItem: function() {
		var grid = this.grid, colCount = this.getColCount();
		return new Ext.menu.CheckItem({
			text: 'Toggle All (' + colCount + ' columns)',
			checked: true,
			hideOnClick: false,
			handler: this.toggleCheckHandler,
			scope: grid
		});
	},
	
	// 'this' scope expected to be 'grid':
	toggleCheckHandler: function(item) {
		var checked = ! item.checked, cm = this.getColumnModel();
					
		var msg = checked ? 'Toggling all on' : 'Toggling all off';
		
		var first_skipped = false;
		var fn;
		var totalCount = item.parentMenu.items.getCount();
		var mask =  myMask = new Ext.LoadMask(item.parentMenu.getEl(), {msg:msg});
		mask.show();
		fn = function(ndx) {
			
			mask.el.mask(msg + " (" + Math.round(((ndx+1)/totalCount)*100) + "%)", mask.msgCls);
			
			var i = item.parentMenu.items.itemAt(ndx);
			if(!i || !item.parentMenu.isVisible()) { mask.hide(); return; }
			if(item !== i && i.setChecked && !i.disabled) {
				if(i.checked == checked) { return fn.defer(0,this,[ndx+1]); }
				// when unchecking all, leave one checked
				if(!checked && i.checked && !first_skipped) {
					first_skipped = true;
					return fn.defer(0,this,[ndx+1]);
				}
				i.setChecked(checked,true);
				var itemId = i.getItemId(), index = cm.getIndexById(itemId.substr(4));
				if (index != -1) { cm.setHidden(index, !checked); }
			}
			fn.defer(1,this,[ndx+1]);
		};
		fn.defer(0,this,[0]);
	}
	
});
Ext.preg('appgrid-toggle-all-cols',Ext.ux.RapidApp.Plugin.AppGridToggleAllCols);


Ext.ux.RapidApp.Plugin.AppGridFilterCols = Ext.extend(Ext.ux.RapidApp.Plugin.AppGridColMenuPlug,{
	
	testMatch: function(item,str) {
		// If the string is not set, match is true per default:
		if(!str || str == '' || !item.text) { return true; }
		
		str = str.toLowerCase();
		var text = item.text.toLowerCase();
		
		// Test menu item text
		if(text.indexOf(str) != -1) { return true; }
		var column = this.menuItemToColumn(item);
		if (column) {
			// Test column name
			if(column.name) {
				var text = column.name.toLowerCase();
				if(text.indexOf(str) != -1) { return true; }
			}
			// Test column header:
			if(column.header) {
				var text = column.header.toLowerCase();
				if(text.indexOf(str) != -1) { return true; }
			}
		}
		return false;
	},
	
	menuItemToColumn: function(item) {
		var colModel = this.cm,
			itemId = item.getItemId(),
			colId = itemId.substr(4);
		
		return colModel.config[colId];
	},
	
	filterByString: function(str) {
		if(str == '') { str = null; }
		if(!this.colMenu.isVisible()) { return; }
		
		var past_sep,past_label,add_at,remove,match_count = 0;
		this.colMenu.items.each(function(item,ndx){
			if(!past_sep) {
				if(this.isSeparator(item)){ past_sep = true; }
				return;
			}
			else if (!past_label) {
				if(str) { add_at = ndx; }
				past_label = true;
				if(item.isFilterLabel) {
					remove = item;
					return;
				}
			}
			
			var match = this.testMatch(item,str);
			if(match) { match_count++; }
			
			if(!item.hidden) {
				if(!match) {
					item.setVisible(false);
					item.isFiltered = true;
				}
			}
			else {
				if(match && item.isFiltered) {
					delete item.isFiltered;
					item.setVisible(true);
				}
			}
		},this);
		
		//if(remove) { this.colMenu.remove(remove,true); }
		if(remove) { 
			var liEl = remove.el.parent('li');
			this.colMenu.remove(remove,true);
			// this appears to be an Ext bug:
			if(liEl && liEl.dom) { Ext.removeNode(liEl.dom); }
		}
		
		if(add_at) {
			this.colMenu.insert(add_at,{
				isFilterLabel: true,
				xtype: 'label',
				html: '<b><i><center>Filtered (' + match_count + ' columns)</center></i></b>'
			});
		}
	},
	
	getColItem: function() {
		return {
			xtype:'textfield',
			emptyText: 'Type to Filter Column List',
			emptyClass: 'field-empty-text',
			width: '200px',
			enableKeyEvents:true,
			listeners: {
				render: {
					scope: this,
					fn: function(field) {
						field.filterTask = new Ext.util.DelayedTask(function(f){
							this.filterByString(f.getValue());
						},this,[field]);
					}
				},
				keyup: {
					scope: this,
					buffer: 150,
					fn: function(field, e) {
						if(field.filterTask) { field.filterTask.delay(500); }
					}
				}
			}
		};
	}
});
Ext.preg('appgrid-filter-cols',Ext.ux.RapidApp.Plugin.AppGridFilterCols);




Ext.ux.RapidApp.Plugin.AppGridBatchEdit = Ext.extend(Ext.util.Observable,{
	
	init: function(grid) {
		this.grid = grid;
		grid.on('render',this.onRender,this);
		grid.on('rowcontextmenu',this.onRowcontextmenu,this);
	},
	
	onRender: function() {
		
		var grid = this.grid, 
			store = grid.getStore(), 
			menu = grid.getOptionsMenu();
		
		if(!grid.batch_update_url || !store.api.update || !menu) { 
			return; 
		}
		
		menu.add(this.getMenu());
		
		store.on('load',this.updateEditMenus,this);
		grid.getSelectionModel().on('selectionchange',this.updateEditMenus,this);
	},
	
	onRowcontextmenu: function(grid,rowIndex,e) {
		count = this.selectedCount();
		if(count <= 1) { return; }
		
		//stop browser menu:
		e.stopEvent();
		
		var menuItems = [{
			text: 'Batch Modify Selected Records (' + count + ')',
			iconCls: 'ra-icon-table-edit-row',
			scope: this,
			handler: this.doBatchEditSelected
		}];

		var menu = new Ext.menu.Menu({ items: menuItems });
		var pos = e.getXY();
		pos[0] = pos[0] + 10;
		pos[1] = pos[1] + 5;
		menu.showAt(pos);
	},
	
	getMenu: function() {
		if(!this.editMenu) {
			this.editMenu = new Ext.menu.Item({
				text: 'Batch Modify',
				iconCls: 'ra-icon-table-sql-edit',
				hideOnClick: false,
				menu: [
					{
						itemId: 'all',
						text: 'All Active Records',
						iconCls: 'ra-icon-table-edit-all',
						scope: this,
						handler: this.doBatchEditAll
					},
					{
						itemId: 'selected',
						text: 'Selected Records',
						iconCls: 'ra-icon-table-edit-row',
						scope: this,
						handler: this.doBatchEditSelected
					}
				]
			});
		}
		return this.editMenu;
	},
	
	getStoreParams: function() {
		var store = this.grid.getStore();
		
		var params = {};
		Ext.apply(params,store.lastOptions.params);
		Ext.apply(params,store.baseParams);
			
		return params;
	},
	
	getSelectedIds: function() {
		var selections = this.grid.getSelectionModel().getSelections();
		var ids = [];
		Ext.each(selections,function(item){
			ids.push(item.id);
		},this);
		
		return ids;
	},
	
	getBatchEditSpec: function(sel) {
		
		var opt = { read_params: this.getStoreParams() };
		
		if(sel) {
			var ids = this.getSelectedIds();
			Ext.apply(opt,{
				selectedIds: ids,
				count: ids.length
			});
		}
		else {
			opt.count = this.grid.getStore().getTotalCount();
		}
		
		return opt;
	},
	
	
	selectedCount: function() {
		return this.grid.getSelectionModel().getSelections().length;
	},
	
	updateEditMenus: function() {
		var menu = this.getMenu().menu;
		if(!menu) { return; }
		
		//console.log(this.getEditColumns().length);
		
		var allItem = menu.getComponent('all');
		var count = this.grid.getStore().getTotalCount();
		allItem.setText('All Active Records (' + count + ')');
		allItem.setDisabled(count <= 1);
		
		var selItem = menu.getComponent('selected');
		count = this.selectedCount();
		selItem.setText('Selected Records (' + count + ')');
		selItem.setDisabled(count <= 1);
		
	},
	
	getEditColumns: function() {
		var columns = [];
		var cm = this.grid.getColumnModel();
		for (i in cm.config) {
			var c = cm.config[i];
			// Only show editable, non-hidden columns:
			var valid = false;
			if(!c.hidden && cm.isCellEditable(i,0)) { valid = true; }
			if(typeof c.allow_batchedit != 'undefined' && !c.allow_batchedit) { valid = false; }
			
			if(valid) { 
				columns.push(c); 
			}
		}
		return columns;
	},
	
	getBatchEditFields: function(fp) {
		//var fp = fp;
		var fields = [];
		var columns = this.getEditColumns();
		Ext.each(columns,function(column) {

			var Field,field;
			if(Ext.isFunction(column.editor.cloneConfig)) {
				field = column.editor.cloneConfig();
				//Field = column.editor;
				if(field.store){ field.store.autoDestroy = false; }
			}
			else {
				//Field = Ext.ComponentMgr.create(field,'textfield');
				Ext.apply(field,column.editor);
			}
			
			Ext.apply(field,{
				name: column.name,
				fieldLabel: column.header || column.name,
				flex: 1,
				disabled: true
			});
			
			// Turn into a component object now:
			Field = Ext.ComponentMgr.create(field,'textfield');
			
			// If this is a textarea with "grow" on:
			// http://www.sencha.com/forum/showthread.php?104490-Solved-Auto-growing-textarea-in-CompositeField&p=498813&viewfull=1#post498813
			Field.on('autosize', function(textarea){
				textarea.ownerCt.doLayout(); // == compositeField.innerCt.doLayout()
			});

			var Toggle = new Ext.form.Checkbox({ itemId: 'toggle' });
			Toggle.on('check',function(checkbox,checked){
				var disabled = !checked
				var labelEl = Field.getEl().parent('div.x-form-item').first('label');
				Field.setDisabled(disabled);
				fp.updateSelections();
				if(disabled){ 
					Field.clearInvalid();
					labelEl.setStyle('font-weight','normal');
					labelEl.setStyle('font-style','normal');
					labelEl.setStyle('color','black');
				}
				else{
					Field.validate();
					labelEl.setStyle('font-weight','bold');
					labelEl.setStyle('font-style','italic');
					labelEl.setStyle('color','green');
				}
			},fp);
			
			var comp_field = {
				xtype: 'compositefield',
				items: [Toggle,Field]
			};
			
			fields.push(
				{ xtype: 'spacer', height: 10 },
				comp_field
			);
			
		},this);
		
		return fields;
	},
	
	doBatchEditAll: function() { this.doBatchEdit(); },
	doBatchEditSelected: function() { this.doBatchEdit(true); },
	
	doBatchEdit: function(sel) {
		var editSpec = this.getBatchEditSpec(sel);
		
		var fp = new Ext.form.FormPanel({
			xtype: 'form',
			frame: true,
			labelAlign: 'right',
			
			//plugins: ['dynamic-label-width'],
			labelWidth: 130,
			labelPad: 15,
			bodyStyle: 'padding: 10px 25px 5px 5px;',
			defaults: { anchor: '-0' },
			autoScroll: true,
			//monitorValid: true,
			buttonAlign: 'right',
			minButtonWidth: 100,
			
			getSaveBtn: function() {
				for (i in fp.buttons) {
					if(fp.buttons[i].name == 'save') { return fp.buttons[i]; }
				}
				return null;
			},
			
			getCount: function() {
				var count = 0;
				fp.items.each(function(itm){
					if(!itm.items) { return; }
					var cb = itm.items.find( // have to reproduce: itm.getComponent('toggle')
						function(i){ if (i.itemId == 'toggle'){ return true; }},
					this);
					if(cb && cb.getValue()) { count++; }
				},this);
				return count;
			},
			
			updateSelections: function() {
				fp.stopMonitoring();
				var count = fp.getCount();
				var saveBtn = fp.getSaveBtn();
				saveBtn.setText('Apply Changes (' + count + ' fields)');
				
				if(count > 0) {
					fp.startMonitoring();
				}
				else {
					saveBtn.setDisabled(true);
				}
			},
			
			buttons: [
				{
					name: 'save',
					text: 'Apply Changes (0 fields)',
					iconCls: 'ra-icon-save-ok',
					width: 175,
					formBind: true,
					disabled: true,
					scope: this,
					handler: function(btn) {
						var data = {};
						fp.getForm().items.each(function(comp_field){
							// get the field out of the composit field. expects 2 items, the checkbox then the field
							// need to do it this way to make sure we call the field's getValue() function
							f = comp_field.items.items[1];
							if(f && !f.disabled && f.name && Ext.isFunction(f.getValue)) {
								data[f.name] = f.getValue();
							}
						},this);
						
						this.postUpdate(editSpec,data,this.win);
					}
				},
				{
					name: 'cancel',
					text: 'Cancel',
					handler: function(btn) {
						this.win.close();
					},
					scope: this
				}
			]
		});
		
		var txt = 'Changes will be applied (identically) to all ' + editSpec.count + ' records in the active search.';
		if(sel) { txt = 'Changes will be applied (identically) to the ' + editSpec.count + ' selected records.'; }

		var items = this.getBatchEditFields(fp);
		
		if(items.length == 0) {
			return Ext.Msg.show({
				icon: Ext.Msg.WARNING,
				title: 'No editable columns to Batch Modify',
				msg: 
					'None of the currently selected columns are batch editable - nothing to Batch Modify.' +
					'<br><br>Select at least one editable column from the Columns menu and try again.',
				buttons: Ext.Msg.OK
			});
		}
		
		items.unshift(
			{ html: '<div class="ra-batch-edit-heading">' +
				'Batch Modify <span class="num">' + editSpec.count + '</span> Records:' +
			'</div>' },
			{ html: '<div class="ra-batch-edit-sub-heading">' +
				'Click the checkboxes below to enter field values to change/update in each record.<br>' + txt +
				'<div class="warn-line"><span class="warn">WARNING</span>: This operation cannot be undone.</div>' +
			'</div>'}
		);
			
		items.push(
			{ xtype: 'spacer', height: 15 },
			{ html: '<div class="ra-batch-edit-sub-heading">' +
				'<div class="warn-line">' +
					'<i><span class="warn">Note</span>: available fields limited to visible + editable columns</i>' +
				'</div>'
			}
		);
		
		fp.add(items);

		var title = 'Batch Modify Active Records';
		if(sel) { title = 'Batch Modify Selected Records'; }
		
		if(this.win) {
			this.win.close();
		}
		
		this.win = new Ext.Window({
			title: title,
			layout: 'fit',
			width: 600,
			height: 500,
			minWidth: 475,
			minHeight: 350,
			closable: true,
			closeAction: 'close',
			modal: true,
			items: fp
		});
		
		this.win.show();
	},
	
	postUpdate: function(editSpec,data,win){
		
		/* --------------------------------------------------------------------------------- */
		// If selectedIds is set, it means we're updating records in the local store and
		// we can update them them locally, no need to go to the server with a custom
		// batch_edit call (use a regular store/api update). This is essentially a 
		// *completely* different mechanism, although it is transparent to the user:
		if(editSpec.selectedIds) { return this.localUpdate(editSpec.selectedIds,data,win); }
		/* --------------------------------------------------------------------------------- */
		
		//var Conn = new Ext.data.Connection();
		var Conn = Ext.ux.RapidApp.newConn({ timeout: 300000 }); //<-- 5 minute timeout
		
		var myMask = new Ext.LoadMask(win.getEl(), {msg:"Updating Multiple Records - This may take several minutes..."});
		var showMask = function(){ myMask.show(); }
		var hideMask = function(){ myMask.hide(); }
		
		Conn.on('beforerequest', showMask, this);
		Conn.on('requestcomplete', hideMask, this);
		Conn.on('requestexception', hideMask, this);
		
		editSpec.update = data;
		
		Conn.request({
			url: this.grid.batch_update_url,
			params: { editSpec: Ext.encode(editSpec) },
			scope: this,
			success: function(){
				win.close();
				this.grid.getStore().reload();
			}
		});
	},
	
	localUpdate: function(ids,data,win) {
		var store = this.grid.getStore();
			
		Ext.each(ids,function(id) {
			var Record = store.getById(id);
			Record.beginEdit();
			for (i in data) { 
				Record.set(i,data[i]); 
			}
			Record.endEdit();
		},this);
		
		// create a single-use load mask for the update:
		if(store.hasPendingChanges()) { //<-- it is *possible* nothing was changed
			
			var colnames = this.grid.currentVisibleColnames.call(this.grid);
			for (i in data) { colnames.push(i); }
			store.setBaseParam('columns',Ext.encode(colnames));
			store.setBaseParam('batch_update',true);
			
			var lMask = new Ext.LoadMask(win.getEl(),{ msg:"Updating Multiple Records - Please Wait..."});
			lMask.show();
			var hide_fn;
			hide_fn = function(){ 
				lMask.hide(); 
				store.un('write',hide_fn);
				win.close();
			};
			store.on('write',hide_fn,this);
			store.save();
			delete store.baseParams.batch_update; //<-- remove the tmp added batch_update param
		}
		else {
			// if we're here it means there was nothing to change (all the records already had the values
			// that were specified). Call store save for good measure and close the window:
			store.save();
			return win.close();
		}
	}
});
Ext.preg('appgrid-batch-edit',Ext.ux.RapidApp.Plugin.AppGridBatchEdit);





/*
 Ext.ux.RapidApp.Plugin.RelativeDateTime
 2012-04-08 by HV

 Plugin for DateTime fields that allows and processes relative date strings.
*/
Ext.ux.RapidApp.Plugin.RelativeDateTime = Ext.extend(Ext.util.Observable,{
	init: function(cmp) {
		this.cmp = cmp;
		var plugin = this;
		
		// Override parseDate, redirecting to parseRelativeDate for strings 
		// starting with '-' or '+', otherwise, use native behavior
		var native_parseDate = cmp.parseDate;
		cmp.parseDate = function(value) {
			if(plugin.isDurationString.call(plugin,value)) {
				var ret = plugin.parseRelativeDate.apply(plugin,arguments);
				if(ret) { return ret; }
			}
			if(native_parseDate) { return native_parseDate.apply(cmp,arguments); }
		}
		
		if(cmp.noReplaceDurations) {
			var native_setValue = cmp.setValue;
			cmp.setValue = function(value) {
				if(plugin.isDurationString.call(plugin,value)) {
					// Generic setValue function on the non-inflated value:
					return Ext.form.TextField.superclass.setValue.call(cmp,value);
				}
				return native_setValue.apply(cmp,arguments);
			};
		}
		
		cmp.beforeBlur = function() {
			var value = cmp.getRawValue(),
				v = cmp.parseDate(value);
			
			if(plugin.isDurationString.call(plugin,value)) {
				if(v) {
					// save the duration string before it gets overwritten:
					cmp.lastDurationString = value;
					
					// Don't inflate/replace duration strings with parsed dates in the field
					if(cmp.noReplaceDurations) { return; }
				}
				else {
					cmp.lastDurationString = null;
				}
			}
			
			//native DateField beforeBlur behavior:
			if(v) {
				// This is the place where the actual value inflation occurs. In the native class,
				// this is destructive, in that after this point, the original raw value is 
				// replaced and lost. That is why we save it in 'durationString' above, and also
				// why we optionally skip this altogether if 'noReplaceDurations' is true:
				cmp.setValue(v);
			}
		};
		
		cmp.getDurationString = function() {
			var v = cmp.getRawValue();
			// If the current value is already a valid duration string, return it outright:
			if(plugin.parseRelativeDate.call(plugin,v)) { 
				cmp.lastDurationString = v;
				return v; 
			}
			
			if(!cmp.lastDurationString) { return null; }
			
			// check to see that the current/inflated value still matches the last
			// duration string by parsing and rendering it and the current field value. 
			// If they don't match, it could mean that a different, non duration value 
			// has been entered, or, if the value is just different (such as in places
			// where the field is reused in several places, like grid editors):
			var dt1 = cmp.parseDate(v);
			var dt2 = cmp.parseDate(cmp.lastDurationString);
			if(dt1 && dt2 && cmp.formatDate(dt1) == cmp.formatDate(dt2)) {
				return cmp.lastDurationString;
			}
			
			cmp.lastDurationString = null;
			return null;
		};
		
		
		if(Ext.isFunction(cmp.onTriggerClick)) {
			var native_onTriggerClick = cmp.onTriggerClick;
			cmp.onTriggerClick = function() {
				if(cmp.disabled){ return; }
				
				// Sets cmp.menu before the original onTriggerClick has a chance to:
				plugin.getDateMenu.call(plugin);
				
				native_onTriggerClick.apply(cmp,arguments);
			}
		}
		
	},
	
	startDateKeywords: {
		
		now: function() {
			return new Date();
		},
		
		thisminute: function() {
			var dt = new Date();
			dt.setSeconds(0);
			dt.setMilliseconds(0);
			return dt;
		},
		
		thishour: function() {
			var dt = new Date();
			dt.setMinutes(0);
			dt.setSeconds(0);
			dt.setMilliseconds(0);
			return dt;
		},
		
		thisday: function() {
			var dt = new Date();
			return dt.clearTime();
		},
		
		today: function() {
			var dt = new Date();
			return dt.clearTime();
		},
		
		thisweek: function() {
			var dt = new Date();
			var day = parseInt(dt.format('N'));
			//day++; if(day > 7) { day = 1; } //<-- shift day 1 from Monday to Sunday
			var subtract = 1 - day;
			return dt.add(Date.DAY,subtract).clearTime();
		},
		
		thismonth: function() {
			var dt = new Date();
			return dt.getFirstDateOfMonth();
		},
		
		thisquarter: function() {
			var dt = new Date();
			dt = dt.getFirstDateOfMonth();
			var month = parseInt(dt.format('n'));
			var subtract = 0;
			if(month > 0 && month <= 3) {
				subtract = month - 1;
			}
			else if(month > 3 && month <= 6) {
				subtract = month - 4;
			}
			else if(month > 6 && month <= 9) {
				subtract = month - 7;
			}
			else {
				subtract = month - 10;
			}
			return dt.add(Date.MONTH,0 - subtract).clearTime();
		},
		
		thisyear: function() {
			var dt = new Date();
			var date_string = '1/01/' + dt.format('Y') + ' 00:00:00';
			return new Date(date_string);
		}
	},
	
	getKeywordStartDt: function(keyword) {
		keyword = keyword.replace(/\s*/g,''); // <-- strip whitespace
		keyword = keyword.toLowerCase();
		var fn = this.startDateKeywords[keyword] || function(){ return null; };
		return fn();
	},
	
	isDurationString: function(str) {
		if(str && Ext.isString(str)) {
			if(str.search(/[\+\-]/) != -1){ return true; };
			if(this.getKeywordStartDt(str)){ return true; };
		}
		return false;
	},
	
	parseRelativeDate: function(value) {
		var dt = this.getKeywordStartDt(value);
		if(dt) { return dt; } //<-- if the supplied value is a start keyword alone
		
		// find the offset of the sign char (the first + or -):
		var pos = value.search(/[\+\-]/);
		if(pos == -1) { return null; }
		if(pos > 0) {
			// If we are here then it means a custom start keyword was specified:
			var keyword = value.substr(0,pos);
			dt = this.getKeywordStartDt(keyword);
		}
		else {
			// Default start date/keyword "now":
			dt = this.getKeywordStartDt('now');
		}
		
		if(!dt) { return null; }
		
		var sign = value.substr(pos,1);
		if(sign != '+' && sign != '-') { return null; }
		var str = value.substr(pos+1);

		var parts = this.extractDurationParts(str);
		if(!parts) { return null; }
		
		var invalid = false;
		Ext.each(parts,function(part){
			if(invalid) { return; }
			
			if(sign == '-') { part.num = '-' + part.num; }
			var num = parseInt(part.num);
			if(num == NaN) { invalid = true; return; }
			
			var newDt = this.addToDate(dt,num,part.unit);

			if(!newDt) { invalid = true; return; }
			dt = newDt;
		},this);
		
		return invalid ? null : dt;
	},
	
	extractDurationParts: function(str) {
		
		// strip commas and whitespace:
		str = str.replace(/\,/g,'');
		str = str.replace(/\s*/g,'');
		
		var parts = [];
		while(str.length > 0) {
			var pos,num,unit;
			
			// find the offset of the first letter after some numbers
			pos = str.search(/\D/);
			
			// If there are no numbers (pos == 0) or we didn't find any letters (pos == -1)
			// this is an invalid duration string, return:
			if(pos <= 0) { return null; }
			
			// this is the number:
			num = str.substr(0,pos);
			
			// remove it off the front of the string before proceding:
			str = str.substr(pos);
			
			// find the offset of the next number after some letters
			pos = str.search(/\d/);
			
			// if no numbers were found, this must be the last part
			if(pos == -1) {
				// this is the unit:
				unit = str;
				
				// empty the string
				str = '';
			}
			else {
				// this is the unit:
				unit = str.substr(0,pos);
				
				// remove it off the front of the string before proceding:
				str = str.substr(pos);
			}
			
			// Make sure num is a valid int/number:
			if(!/^\d+$/.test(num)) { return null; }
			
			parts.push({num:num,unit:unit});
		}
		
		return parts;
	},
	
	unitMap: {
		y			: Date.YEAR,
		year		: Date.YEAR,
		years		: Date.YEAR,
		yr			: Date.YEAR,
		yrs		: Date.YEAR,
		
		m			: Date.MONTH,
		mo			: Date.MONTH,
		month		: Date.MONTH,
		months	: Date.MONTH,
		
		d			: Date.DAY,
		day		: Date.DAY,
		days		: Date.DAY,
		dy			: Date.DAY,
		dys		: Date.DAY,
		
		h			: Date.HOUR,
		hour		: Date.HOUR,
		hours		: Date.HOUR,
		hr			: Date.HOUR,
		hrs		: Date.HOUR,
		
		i			: Date.MINUTE,
		mi			: Date.MINUTE,
		min		: Date.MINUTE,
		mins		: Date.MINUTE,
		minute	: Date.MINUTE,
		minutes	: Date.MINUTE,
		
		s			: Date.SECOND,
		sec		: Date.SECOND,
		secs		: Date.SECOND,
		second	: Date.SECOND,
		second	: Date.SECOND
	},
	
	addToDate: function(dt,num,unit) {
		dt = dt || new Date();
		
		unit = unit.toLowerCase();
		
		// custom support for "weeks":
		if(unit == 'w' || unit == 'week' || unit == 'weeks' || unit == 'wk' || unit == 'wks') {
			unit = 'days';
			num = num*7;
		}
		
		// custom support for "quarters":
		if(unit == 'q' || unit == 'quarter' || unit == 'quarters' || unit == 'qtr' || unit == 'qtrs') {
			unit = 'months';
			num = num*3;
		}
		
		var interval = this.unitMap[unit];
		if(!interval) { return null; }
		
		return dt.add(interval,num);
	},
	
	getDateMenu: function() {
		
		if(!this.cmp.menu) {
			
			var menu = new Ext.menu.DateMenu({
				hideOnClick: false,
				focusOnSelect: false
			});
			
			menu.on('afterrender',function(){
				var el = menu.getEl();
				var existBtn = el.child('td.x-date-bottom table');
				
				if(existBtn) {
					existBtn.setStyle('float','left');
					
					if(this.cmp.allowBlank) { this.addSelectNoneBtn(existBtn); }
					
					newEl = existBtn.insertSibling({ tag: 'div', style: 'float:right;' },'after');
					var relBtn = new Ext.Button({
						iconCls: 'ra-icon-clock-run',
						text: 'Relative Date',
						handler: this.showRelativeDateMenu,
						scope: this
					});
					relBtn.render(newEl);
				}

			},this);
			
			this.cmp.menu = menu;
		}
		
		return this.cmp.menu;
	},
	
	addSelectNoneBtn: function(existBtn) {
		
		var newEl = existBtn.insertSibling({ tag: 'div', style: 'float:left;' },'after');
		var noneBtn = new Ext.Button({
			text: '<span style="font-size:.9em;color:grey;">(None)</span>',
			handler: function(){ 
				this.cmp.setValue(null); 
				this.cmp.resumeEvents();
				this.cmp.fireEvent('blur');
				this.cmp.menu.hide();
			},
			scope: this
		});
		noneBtn.render(newEl);
		
	},
	
	showRelativeDateMenu: function(btn,e) {
		var dmenu = this.cmp.menu, rmenu = this.getRelativeDateMenu();
		// the dmenu automatically hides itself:
		rmenu.showAt(dmenu.getPosition());
	},
	
	getRelativeDateMenu: function() {
		var plugin = this;
		if(!this.relativeDateMenu) {
			var menu = new Ext.menu.Menu({
				style: 'padding-top:5px;padding-left:5px;padding-right:5px;',
				width: 330,
				layout: 'anchor',
				showSeparator: false,
				items: [
					{ 
						xtype: 'label',
						html: '<div class="ra-relative-date">' + 
							'<div class="title">Relative Date/Time</div>' + 
							'<div class="sub">' + 
								'Enter a time length/duration for a date/time <i>relative</i> to the current time. ' +
								'Prefix with a minus <span class="mono">(-)</span> for a date in the past or a plus ' + 
								'<span class="mono">(+)</span> for a date in the future.  ' +
							'</div>' +
							'<div class="sub">' + 
								'You may optionally prepend a referece date keyword &ndash; <span class="mono">today</span>, <span class="mono">this minute</span>, <span class="mono">this hour</span>, <span class="mono">this week</span>, <span class="mono">this month</span>, <span class="mono">this quarter</span> or <span class="mono">this year</span> &ndash; for an even date/threshold to use instead of the current date and time.' +
							'</div>' +
							'<div class="sub">' + 
								'The date calculation of your input is shown as you type.' +
							'</div>' +
							'<div class="examples">Example inputs:</div>' + 
							
							'<table width="85%"><tr>' +
						
							'<td>' +
							'<ul>' +
								'<li>-1 day</li>' +
								'<li>+20 hours, 30 minutes</li>' +
								'<li>today -3d4h18mins</li>' +
								'<li>+2m3d5h</li>' +
								'<li>this hour - 2 hours</li>' +
							'</ul>' + 
							'</td>' +
							
							'<td>' +
							'<ul>' +
								'<li>this quarter+1wks</li>' +
								'<li>-2 years</li>' +
								'<li>this week</li>' +
								'<li>this year - 2 years</li>' +
								'<li>this minute - 30 mins</li>' +
							'</ul>' + 
							'</td>' +
							
							'</tr></table>' +
							
						'</div>' 
					}
				]
			});
			
			menu.okbtn = new Ext.Button({
				text: 'Ok',
				scope: this,
				handler: this.doMenuSaveRelative,
				disabled: true
			});
			
			menu.renderValLabel = new Ext.form.Label({
				html: '&uarr;&nbsp;enter',
				cls: 'ra-relative-date-renderval'
			});
			
			menu.field = new Ext.form.TextField({
				anchor: '100%',
				fieldClass: 'blue-text-code',
				validator: function(v) {
					if(!v) { 
						menu.okbtn.setDisabled(true);
						menu.renderValLabel.setText(
							'<sup>&uarr;</sup>&nbsp;&nbsp;&nbsp;&nbsp;input relative date above&nbsp;&nbsp;&nbsp;&nbsp;<sup>&uarr;</sup>',
						false);
						return true; 
					}
					var dt = plugin.parseRelativeDate.call(plugin,v);
					var test = dt ? true : false;
					menu.okbtn.setDisabled(!test); 
					
					var renderVal = test ? '=&nbsp;&nbsp;' +
						dt.format('D, F j, Y (g:i a)') : 
						'<span>invalid relative date</span>';
					menu.renderValLabel.setText(renderVal,false);
					
					return test;
				},
				enableKeyEvents:true,
				listeners:{
					keyup:{
						scope: this,
						buffer: 10,
						fn: function(field, e) {
							if (field.isVisible() && Ext.EventObject.ENTER == e.getKey()){
								this.doMenuSaveRelative();
							}
						}
					}
				}
			});
			
			menu.add(menu.field,menu.renderValLabel);
			
			menu.add({
				xtype: 'panel',
				buttonAlign: 'center',
				border: false,
				buttons: [
					{
						xtype: 'button',
						text: 'Cancel',
						handler: function() {
							menu.hide();
						}
					},
					menu.okbtn
				]
			});
			
			menu.on('show',function(){
				
				//Disable the menu keyNav to allow arrow keys to work in fields within the menu:
				menu.keyNav.disable();
				
				this.cmp.suspendEvents();
				var field = menu.field;
				field.setValue(this.cmp.getDurationString());
				field.focus(false,50);
				field.focus(false,200);
				field.setCursorPosition(1000000);
			},this);
			
			menu.on('beforehide',function(){
				
				var field = menu.field;
				
				var value = field.getValue();
				if(!value || value == '' || !field.isValid()) {
					// If the input field isn't valid then the real field wasnt updated
					// (by ENTER keystroke in input field listener) and it didn't call blur.
					// refocus the field:
					this.cmp.focus(false,50);
				}
				
				return true;
			},this);
			
			menu.on('hide',function(){
				this.cmp.resumeEvents();
			},this);
			
			this.relativeDateMenu = menu;
		}
		return this.relativeDateMenu;
	},
	
	doMenuSaveRelative: function() {
		
		var menu = this.relativeDateMenu;
		if(!menu || !menu.isVisible()) { return; }
		var field = menu.field;
		if(!field) { return; }
		
		var v = field.getValue();
		if(v && v != '' && field.isValid()){
			this.cmp.setValue(v);
			this.cmp.resumeEvents();
			this.cmp.fireEvent('blur');
			menu.hide();
		}
	}
	
});
Ext.preg('form-relative-datetime',Ext.ux.RapidApp.Plugin.RelativeDateTime);


// This is basically a clone of the logic in grid col filters plugin, reproduced for
// general purpose menus. There are a few special issues with the col menu that it needs
// to still be separate, for now, but this duplicate logic needs to be consolidated
// at some point:
Ext.ux.RapidApp.Plugin.MenuFilter = Ext.extend(Ext.util.Observable,{
	
	maxSeparatorPosition: 4,
	
	init: function(menu) {
		this.menu = menu;
		menu.on('beforeshow',this.onBeforeshow,this);
		menu.on('show',this.onShow,this);
	},
	
	onBeforeshow: function() {

		//Disable the menu keyNav to allow arrow keys to work in fields within the menu:
		if(this.menu.keyNav){ this.menu.keyNav.disable(); }
		
		if(!this.menu.getComponent('filteritem')) {
			var filteritem = this.getColItem();
			if(!filteritem) { return; }
			//var pos = this.getInsertPosition();
			//this.menu.insert(pos,filteritem);
			this.menu.insert(0,filteritem,'-');
		}
	},
	
	onShow: function() {
		this.autoSizeField.defer(20,this);
		
		if(this.menu.autoFocusFilter) { 
			this.focusFilter();
		}
	},
	
	autoSizeField: function() {
		var field = this.menu.getComponent('filteritem');
		if(field) {
			field.setWidth(this.menu.getWidth() - 25);
		}
	},
	
	focusFilter: function() {
		var field = this.menu.getComponent('filteritem');
		if(field) { 
			field.focus(false,50);
			field.focus(false,200);
		}
	},
	
	getColCount: function() {
		return this.menu.items.getCount();
	},
	
	isSeparator: function(item) {
		if((Ext.isObject(item) && item.itemCls == 'x-menu-sep') || item == '-') {
			return true;
		}
		return false;
	},
	
	/*
	getExistingSeparatorIndex: function() {
		var items = this.menu.items.items;
		for (ndx in items) {
			if(ndx > this.maxSeparatorPosition) { return -1; }
			if(this.isSeparator(items[ndx])) { return ndx; }
		}
		return -1;
	},
	
	getInsertPosition: function() {
		var pos = this.getExistingSeparatorIndex();
		if(pos == -1) { 
			this.menu.insert(0,'-');
			return 0;
		}
		return pos;
	},
	*/
	
	testMatch: function(item,str) {
		// If the string is not set, match is true per default:
		if(!str || str == '' || !item.text) { return true; }
		
		str = str.toLowerCase();
		var text = item.text.toLowerCase();
		
		// Test menu item text
		if(text.indexOf(str) != -1) { return true; }
		
		return false;
	},
	
	filterByString: function(str) {
		if(str == '') { str = null; }
		if(!this.menu.isVisible()) { return; }
		
		var past_sep,past_label,add_at,remove,match_count = 0;
		this.menu.items.each(function(item,ndx){
			if(!past_sep) {
				if(this.isSeparator(item)){ past_sep = true; }
				return;
			}
			else if (!past_label) {
				if(str) { add_at = ndx; }
				past_label = true;
				if(item.isFilterLabel) {
					remove = item;
					return;
				}
			}
			
			var match = this.testMatch(item,str);
			if(match) { match_count++; }
			
			if(!item.hidden) {
				if(!match) {
					item.setVisible(false);
					item.isFiltered = true;
				}
			}
			else {
				if(match && item.isFiltered) {
					delete item.isFiltered;
					item.setVisible(true);
				}
			}
		},this);
		
		if(remove) { 
			var liEl = remove.el.parent('li');
			this.menu.remove(remove,true);
			// this appears to be an Ext bug:
			if(liEl && liEl.dom) { Ext.removeNode(liEl.dom); }
		}
		
		if(add_at) {
			this.menu.insert(add_at,{
				isFilterLabel: true,
				xtype: 'label',
				html: '<b><i><center>Filtered (' + match_count + ' items)</center></i></b>'
			});
		}
		
		this.menu.doLayout();
	},
	
	getColItem: function() {
		return {
			xtype:'textfield',
			itemId: 'filteritem',
			emptyText: 'Type to Filter List',
			emptyClass: 'field-empty-text',
			width: 100, //<-- should be less than the minWidth of menu for proper auto-sizing
			enableKeyEvents:true,
			listeners: {
				render: {
					scope: this,
					fn: function(field) {
						field.filterTask = new Ext.util.DelayedTask(function(f){
							this.filterByString(f.getValue());
						},this,[field]);
					}
				},
				keyup: {
					scope: this,
					buffer: 150,
					fn: function(field, e) {
						if(field.filterTask) { field.filterTask.delay(500); }
					}
				}
			}
		};
	}
});
Ext.preg('menu-filter',Ext.ux.RapidApp.Plugin.MenuFilter);



/*
 Ext.ux.RapidApp.Plugin.GridEditAdvancedConfig
 2012-11-08 by HV

 Plugin that allows editing of the special 'advanced_config' of the component
*/
Ext.ux.RapidApp.Plugin.GridEditAdvancedConfig = Ext.extend(Ext.util.Observable,{
	init: function(grid) {
		this.grid = grid;
		grid.on('afterrender',this.onAfterRender,this);
	},
	
	onAfterRender: function(){
		menu = this.grid.getOptionsMenu();
		if(menu) { menu.add(this.getMenuItem()); }
		
		// Designed to work specifically with AppTab's context menu system:
		if(this.grid.ownerCt) {
			this.grid.ownerCt.getTabContextMenuItems = 
				this.getTabContextMenuItems.createDelegate(this);
		}
	},
	
	getTabContextMenuItems: function() {
		return [ this.getMenuItem() ];
	},
	
	getMenuItem: function() {
		return {
			xtype: 'menuitem',
			text: 'Edit Advanced Config',
			iconCls: 'ra-icon-bullet-wrench',
			handler: this.showAdvancedConfigWin,
			scope: this
		};
	},
	
	showAdvancedConfigWin: function() {
		
		var json = this.grid.store.advanced_config_json;
		json = json || (
			this.grid.store.advanced_config ? 
				Ext.encode(this.grid.store.advanced_config) : ''
		);
		
		var fp;
		
		var saveFn = function(btn) {
			var form = fp.getForm();
			var cb = form.findField('active');
			var jsonf = form.findField('json_data');
			
			// Doing this instead of just called getFieldValues() because that doesn't
			// return the json_data when the field is disabled
			var data = {};
			if(cb && jsonf) {
				data.active = cb.getValue();
				data.json_data = jsonf.getValue();
				this.grid.store.advanced_config_active = data.active;
				this.grid.store.advanced_config_json = data.json_data;
			}
			
			this.win.close();
			
			// Apply the config immediately:
			if(btn.name == 'apply' && this.grid.ownerCt && this.grid.ownerCt.ownerCt) {
				var tab = this.grid.ownerCt, tp = tab.ownerCt;
				if(Ext.isFunction(tp.loadContent) && Ext.isObject(tab.loadContentCnf)) {
					var cnf = tab.loadContentCnf;
					var extra_cnf = {
						update_cmpConfig: function(conf) {
							if(conf.store) {
								conf.store.advanced_config_active = data.active;
								conf.store.advanced_config_json = data.json_data;
							}
						}
					};
					tp.remove(tab);
					tp.loadContent(cnf,extra_cnf);
				}
			}
		};
		
		fp = new Ext.form.FormPanel({
			xtype: 'form',
			frame: true,
			labelAlign: 'right',
			
			//plugins: ['dynamic-label-width'],
			labelWidth: 160,
			labelPad: 15,
			bodyStyle: 'padding: 10px 10px 5px 5px;',
			defaults: { anchor: '-0' },
			autoScroll: true,
			monitorValid: true,
			buttonAlign: 'right',
			minButtonWidth: 100,
			
			items: [
				{
					name: 'active',
					xtype: 'checkbox',
					fieldLabel: 'Advanced Config Active',
					labelStyle: 'font-weight: bold;color:navy;',
					checked: this.grid.store.advanced_config_active ? true : false,
					listeners: {
						check: function(cb,checked) {
							var json_field = cb.ownerCt.getComponent('json_data');
							json_field.setDisabled(!checked);
						}
					}
				},
				{ xtype: 'spacer', height: 10 },
				{
					name: 'json_data',
					itemId: 'json_data',
					xtype: 'textarea',
					style: 'font-family: monospace;',
					fieldLabel: 'Advanced Config JSON',
					hideLabel: true,
					disabled: this.grid.store.advanced_config_active ? false : true,
					value: json,
					anchor: '-0 -35',
					validator: function(v) {
						if(!v || v == '') { return false; }
						var obj, err;
						try{ obj = Ext.decode(v) }catch(e){ err = e; };
						if(err){ return err; }
						return Ext.isObject(obj);
					}
				}
			],
			
			buttons: [
				{
					name: 'apply',
					text: 'Save &amp; Apply',
					iconCls: 'ra-icon-save-ok',
					width: 175,
					formBind: true,
					scope: this,
					handler: saveFn
				},
				{
					name: 'save',
					text: 'Save',
					iconCls: 'ra-icon-save-ok',
					width: 100,
					formBind: true,
					scope: this,
					handler: saveFn
				},
				{
					name: 'cancel',
					text: 'Cancel',
					handler: function(btn) {
						this.win.close();
					},
					scope: this
				}
			]
		});
		
		if(this.win) {
			this.win.close();
		}
		
		this.win = new Ext.Window({
			title: 'Edit Advanced Config (Experts Only)',
			layout: 'fit',
			width: 600,
			height: 400,
			minWidth: 400,
			minHeight: 250,
			closable: true,
			closeAction: 'close',
			modal: true,
			items: fp
		});
		
		this.win.show();
	}
});
Ext.preg('grid-edit-advanced-config',Ext.ux.RapidApp.Plugin.GridEditAdvancedConfig);



/*
 Ext.ux.RapidApp.Plugin.GridEditRawColumns
 2013-05-27 by HV

 Plugin that allows editing the grid 'view' column configs
*/
Ext.ux.RapidApp.Plugin.GridEditRawColumns = Ext.extend(Ext.util.Observable,{
	init: function(grid) {
		this.grid = grid;
		grid.on('afterrender',this.onAfterRender,this);
	},
	
	onAfterRender: function(){
		menu = this.grid.getOptionsMenu();
		if(menu) { menu.add(this.getMenuItem()); }
		
		// Designed to work specifically with AppTab's context menu system:
		if(this.grid.ownerCt) {
			this.grid.ownerCt.getTabContextMenuItems = 
				this.getTabContextMenuItems.createDelegate(this);
		}
	},
	
	getTabContextMenuItems: function() {
		return [ this.getMenuItem() ];
	},
	
	getMenuItem: function() {
		return {
			xtype: 'menuitem',
			text: 'Edit Column Configs',
			iconCls: 'ra-icon-bullet-wrench',
			handler: this.showAdvancedConfigWin,
			scope: this
		};
	},
	
	showAdvancedConfigWin: function() {
		
    var column_configs = this.grid.getColumnModel().config;
    var columns = {};
    Ext.each(column_configs,function(col){
      var cnf = Ext.copyTo({},col,this.grid.column_allow_save_properties);
      columns[col.name] = cnf;
    },this);
    
    var json = JSON.stringify(columns,undefined,2);
		var fp;
		var saveFn = function(btn) {
			var form = fp.getForm();
			var jsonf = form.findField('json_data');
      
			var data = {};
			if(jsonf) {
				data.json_data = jsonf.getValue();
				data.decoded = Ext.decode(data.json_data);
			}
			
			this.win.close();
			
			// Apply the config immediately:
			if(btn.name == 'apply' && this.grid.ownerCt && this.grid.ownerCt.ownerCt) {
				var tab = this.grid.ownerCt, tp = tab.ownerCt;
				if(Ext.isFunction(tp.loadContent) && Ext.isObject(tab.loadContentCnf)) {
					var cnf = tab.loadContentCnf;
					tp.remove(tab);
					tp.loadContent(cnf,{
						update_cmpConfig: function(conf) {
              
              Ext.each(conf.columns,function(col){
                var saved_col = data.decoded[col.name];
                if(saved_col) {
                  Ext.apply(col,saved_col);
                }
              },this);
						}
					});
				}
			}
		};
		
		fp = new Ext.form.FormPanel({
			xtype: 'form',
			frame: true,
			labelAlign: 'right',
			
			//plugins: ['dynamic-label-width'],
			labelWidth: 160,
			labelPad: 15,
			bodyStyle: 'padding: 10px 10px 5px 5px;',
			defaults: { anchor: '-0' },
			autoScroll: true,
			monitorValid: true,
			buttonAlign: 'right',
			minButtonWidth: 100,
			
			items: [
							{
					name: 'json_data',
					itemId: 'json_data',
					xtype: 'textarea',
					style: 'font-family: monospace;',
					fieldLabel: 'Columns JSON',
					hideLabel: true,
					value: json,
					anchor: '-0 -35',
					validator: function(v) {
						if(!v || v == '') { return false; }
						var obj, err;
						try{ obj = Ext.decode(v) }catch(e){ err = e; };
						if(err){ return err; }
						return Ext.isObject(obj);
					}
				}
			],
			
			buttons: [
				{
					name: 'apply',
					text: 'Apply & Reload',
					iconCls: 'ra-icon-save-ok',
					width: 175,
					formBind: true,
					scope: this,
					handler: saveFn
				},
				
				{
					name: 'cancel',
					text: 'Cancel',
					handler: function(btn) {
						this.win.close();
					},
					scope: this
				}
			]
		});
		
		if(this.win) {
			this.win.close();
		}
		
		this.win = new Ext.Window({
			title: 'Edit Raw Column Configs (Experts Only)',
			layout: 'fit',
			width: 800,
			height: 600,
			minWidth: 400,
			minHeight: 250,
			closable: true,
			closeAction: 'close',
			modal: true,
			items: fp
		});
		
		this.win.show();
	}
});
Ext.preg('grid-edit-raw-columns',Ext.ux.RapidApp.Plugin.GridEditRawColumns);



Ext.ux.RapidApp.Plugin.GridCustomHeaders = Ext.extend(Ext.util.Observable,{
	
  menu_item_id: 'change-column-header',
  
	init: function(grid) {
    this.grid = grid;
		grid.on('render',this.onRender,this);
	},
  
  promptChangeHeader: function() {
    var column = this.getActiveCol();
    if(!column) { return; }
    
    var blank_str = '&#160;';
    var current_header = column.header;
    if (current_header == blank_str) {
      current_header = '';
    }
    
    var fp;
    fp = new Ext.form.FormPanel({
			xtype: 'form',
			frame: true,
			labelAlign: 'right',
			
			//plugins: ['dynamic-label-width'],
			labelWidth: 70,
			labelPad: 15,
			bodyStyle: 'padding: 10px 10px 5px 5px;',
			defaults: { anchor: '-0' },
			autoScroll: true,
			monitorValid: true,
			buttonAlign: 'right',
			minButtonWidth: 100,
			
			items: [
				{
          name: 'colname',
          xtype: 'displayfield',
          fieldLabel: 'Column',
          style: 'bottom:-1px;',
          cls: 'blue-text-code',
          value: column.name
        },
        { xtype: 'spacer', height: 5 },
        {
					name: 'header',
					itemId: 'header',
					xtype: 'textfield',
					fieldLabel: 'Header',
					value: current_header,
					anchor: '-0'
				}
			],
			
			buttons: [
				{
					name: 'apply',
					text: 'Save',
					iconCls: 'ra-icon-save-ok',
					width: 90,
					formBind: true,
					scope: this,
					handler: function() {
            var form = fp.getForm();
            var f = form.findField('header');
            var value = f.getValue();
            if(value != column.header) {
              value = value ? value : blank_str;
              var cm = this.grid.getColumnModel();
              var indx = cm.getIndexById(column.id);
              //cm.setColumnHeader(indx,value);
              column.header = value;
              this.grid.store.custom_headers[column.name] = value;
              this.grid.reconfigure(this.grid.store,cm);
            }
            this.win.close();
          }
				},
				
				{
					name: 'cancel',
					text: 'Cancel',
					handler: function(btn) {
						this.win.close();
					},
					scope: this
				}
			]
		});
		
		if(this.win) {
			this.win.close();
		}
    
    this.win = this.win = new Ext.Window({
			title: 'Change Column Header',
			layout: 'fit',
			width: 400,
			height: 175,
			minWidth: 300,
			minHeight: 150,
			closable: true,
			closeAction: 'close',
			modal: true,
			items: fp
		});
    
    this.win.show(); 
  },
      
	getActiveCol: function() {
    var view = this.grid.getView();
    if (!view || view.hdCtxIndex === undefined) {
      return null;
    }
    return view.cm.config[view.hdCtxIndex];
  },
      
	onRender: function() {
  
    if(!this.grid.store.custom_headers) {
      this.grid.store.custom_headers = {};
    }
		
    var hmenu = this.grid.view.hmenu;
    if(!hmenu) { return; }
    
    // New: check for duplicates, which can happen if the plugin
    // is accidently loaded more than once
    if(hmenu.getComponent(this.menu_item_id)) { return; }

    var index = 0;
    var colsItem = hmenu.getComponent('columns');
    if(colsItem) {
      index = hmenu.items.indexOf(colsItem);
    }
    
    hmenu.insert(index,{
      text: "Change Header",
      itemId: this.menu_item_id,
      iconCls: 'ra-icon-textfield-edit',
      handler:this.promptChangeHeader, 
      scope: this
    });
	}
});
Ext.preg('grid-custom-headers',Ext.ux.RapidApp.Plugin.GridCustomHeaders);


Ext.ux.RapidApp.Plugin.GridToggleEditCells = Ext.extend(Ext.util.Observable,{
	
	init: function(grid) {
    this.grid = grid;
		grid.on('render',this.onRender,this);
	},
  
  onText: '<span style="color:#666666;">Cell Editing On</span>',
  offText: '<span style="color:#666666;">Cell Editing Off</span>',
  onIconCls: 'ra-icon-textfield-check',
  offIconCls: 'ra-icon-textfield-cross',
  
  toggleEditing: function(btn) {
    if(this.grid.store.disable_cell_editing) {
      this.btn.setText(this.onText);
      this.btn.setIconClass(this.onIconCls);
      this.grid.store.disable_cell_editing = false;
    }
    else {
      this.btn.setText(this.offText);
      this.btn.setIconClass(this.offIconCls);
      this.grid.store.disable_cell_editing = true;
    }
  },
  
  beforeEdit: function() {
    return this.grid.store.disable_cell_editing ? false : true;
  },
  
  countEditableColumns: function() {
    var count = 0;
    var cm = this.grid.getColumnModel();
    Ext.each(cm.config,function(col,index){
      // Note: this is specific to the RapidApp1/DataStorePlus API which
      // will change in RapidApp2
      if(typeof col.allow_edit !== "undefined" && !col.allow_edit) { return; }
      // Sadly, isCellEditable() is not consistent because of special DataStorePlus
      // code for 'allow_edit' which is handled above
      if(cm.isCellEditable(index,0)) { count++; }
    },this);
    return count;
  },
      
	onRender: function() {
  
    // allow starting the toggle to off if it hasn't been set yet:
    if( this.grid.toggle_edit_cells_init_off && 
        typeof this.grid.store.disable_cell_editing == 'undefined'
    ) { this.grid.store.disable_cell_editing = true; }
  
    if(!this.grid.store.api.update || this.grid.disable_toggle_edit_cells) {
      return;
    }
    
    // Check that there are actually editable columns:
    if(this.countEditableColumns() == 0) { return; }
    
    var tbar = this.grid.getTopToolbar();
    if(! tbar) { return; }
    var optionsBtn = tbar.getComponent('options-button');
    if(! optionsBtn) { return; }
    
    var index = tbar.items.indexOf(optionsBtn) + 1;
    
    this.btn = new Ext.Button({
      text: this.onText,
      iconCls: this.onIconCls,
      handler:this.toggleEditing, 
      scope: this
    });
    
    if(this.grid.store.disable_cell_editing) {
      this.btn.setText(this.offText);
      this.btn.setIconClass(this.offIconCls);
    }
    tbar.insert(index,this.btn);
    
    this.grid.on('beforeedit',this.beforeEdit,this);
	}
	
});
Ext.preg('grid-toggle-edit-cells',Ext.ux.RapidApp.Plugin.GridToggleEditCells);


// This plugin is uses with the top-level Viewport to convert links to
// hashpath URLs to stay within the Ext/RapidApp tab system. For instance,
// clicking <a href="/foo">foo</a> would be translated to '#!/foo'
//
// Designed to work with normal panels or ManagedIFrames:
Ext.ux.RapidApp.Plugin.LinkClickCatcher = Ext.extend(Ext.util.Observable,{
  
  init: function(cmp) {
    this.cmp = cmp;
    if(Ext.ux.RapidApp.HashNav.INITIALIZED) {
    
      var eventName = this.isIframe() ? 'domready' : 'afterrender';
      this.cmp.on(eventName,function(){
        
        var El = this.cmp.getEl();
        
        // For the special ManagedIFrame case, reach into the iframe
        // and get the inner <body> element to attach the listener:
        if(this.isIframe()) {
          var iFrameEl = this.cmp.getFrame();
          var doc = iFrameEl.dom.contentWindow.document;
          El = new Ext.Element(doc.body);
        }
        
        El.on('click',this.clickInterceptor,this);
        
      },this);
    }
  },
  
  isIframe: function() { return Ext.isFunction(this.cmp.getFrame); },
  
  externalUrlRe: new RegExp('^\\w+://'),
  
  clickInterceptor: function(event) {
    var node = event.getTarget(null,null,true);

    if(! node) { return; }
    
    // Is a link (<a> tag) or is a child of a link tag:
    node = node.is('a') ? node : node.parent('a');
    if(! node) { return; }
    
    // Exclude special "filelinks" created by HtmlEditor:
    if(node.hasClass('filelink')) { return; }
    
    var targetAttr = node.getAttribute('target');
    if(targetAttr) {
      // If we're in an iFrame, ignore links with a target defined *IF* they
      // are iFrame-specific (target='_top' or '_parent'). We never want an
      // iFrame to reload to a new URL, but this won't happen with _top/_parent
      // and in these cases we can still safely honor the target
      if(this.isIframe()) {
        if(targetAttr == '_top' && targetAttr !== '_parent') { return; }
      }
      // If we're not in an iFrame, ignore links with any target at all
      // (i.e. target="_blank", etc):
      else {
        return;
      }
    }
    
    var href = node.getAttribute('href');
    
    // New: Also skip if there is no href, and if the href is exactly
    // '#', stop the event so it doesn't mess with the browser histroy,
    // since href="#" is a standard convention for "no action"
    if(!href || href == '#') { 
      event.stopEvent();
      return; 
    }
    
    // URL is local (does not start with http://, https://, etc)
    // and does not start with '#' (such as the ext tabs with href='#')
    if(href.search('#') !== 0 && href.search('/#') !== 0 && ! this.externalUrlRe.test(href)) {
      // Stop the link click event and convert to hashpath:
      event.stopEvent();
      var hashpath = Ext.ux.RapidApp.HashNav.urlToHashPath(href);
      window.location.hash = hashpath;
    }
  }
  
});
Ext.preg('ra-link-click-catcher',Ext.ux.RapidApp.Plugin.LinkClickCatcher);




Ext.ns('Ext.ux.RapidApp.Plugin.HtmlEditor');

// Creates an empty menu at this.ToolsMenu (looked for by other plugis)
Ext.ux.RapidApp.Plugin.HtmlEditor.ToolsMenu = Ext.extend(Ext.util.Observable, {
	
	text: '<span style="' + 
		'font-weight:bold;color:#444444;padding-right:2px;' +
		'font-family:tahoma,helvetica,sans-serif;' + 
		'">' +
			'Tools' +
	'</span>',
	
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
	},
	
	onRender: function() {
		this.cmp.ToolsMenu = new Ext.menu.Menu();
		var tb = this.cmp.getToolbar();
		this.btn = tb.addButton({
			iconCls: 'ra-icon-bullet-wrench',
			style: 'font-size:1.9em;',
			text: this.text,
			menu: this.cmp.ToolsMenu
		});
	}
});
Ext.preg('htmleditor-toolsmenu',Ext.ux.RapidApp.Plugin.HtmlEditor.ToolsMenu);


Ext.ux.RapidApp.Plugin.HtmlEditor.SimpleCAS_Image = Ext.extend(Ext.ux.form.HtmlEditor.Image,{
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	maxImageWidth: null,
	
	resizeWarn: false,
	
	onRender: function() {
		var btn = this.cmp.getToolbar().addButton({
				text: 'Insert Image',
				iconCls: 'x-edit-image',
				handler: this.selectImage,
				scope: this,
				tooltip: {
					title: this.langTitle
				},
				overflowText: this.langTitle
		});
	},
	
	selectImage: function() {
		
		
		var upload_field = {
			xtype: 'fileuploadfield',
			emptyText: 'Select Image',
			name: 'Filedata',
			buttonText: 'Browse',
			hideLabel: true,
			anchor: '100%'
		};
		
		var fieldset = {
			style: 'border: none',
			hideBorders: true,
			xtype: 'fieldset',
			border: false,
			items:[ upload_field ]
		};
		
		var callback = function(form,res) {
			var img = Ext.decode(res.response.responseText);
			
			if(this.resizeWarn && img.resized) {
				Ext.Msg.show({
					title:'Notice: Image Resized',
					msg: 
						'The image has been resized by the server.<br><br>' +
						'Original Size: <b>' + img.orig_width + 'x' + img.orig_height + '</b><br><br>' +
						'New Size: <b>' + img.width + 'x' + img.height + '</b>'
					,
					buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.INFO
				});
			}
			else if (this.resizeWarn && img.shrunk) {
				Ext.Msg.show({
					title:'Notice: Oversized Image Shrunk',
					msg: 
						'The image is oversized and has been pre-shrunk for display <br>' +
						'purposes (however, you can click/drag it larger).<br><br>' +
						'Actual Size: <b>' + img.orig_width + 'x' + img.orig_height + '</b><br><br>' +
						'Displayed Size: <b>' + img.width + 'x' + img.height + '</b>'
					,
					buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.INFO
				});
			}
			
			img.link_url = '/simplecas/fetch_content/' + img.checksum + '/' + img.filename;
			this.insertImage(img);
		};
		
		var url = '/simplecas/upload_image';
		if(this.maxImageWidth) { url += '/' + this.maxImageWidth; }
		
		Ext.ux.RapidApp.WinFormPost.call(this,{
			title: 'Insert Image',
			width: 430,
			height:140,
			url: url,
			useSubmit: true,
			fileUpload: true,
			fieldset: fieldset,
			success: callback
		});
	},

	insertImage: function(img) {
		if(!this.cmp.activated) {
			this.cmp.onFirstFocus();
		}
		this.cmp.insertAtCursor(
			'<img src="' + img.link_url + '" width=' + img.width + ' height=' + img.height + '>'
		);
	}
});
Ext.preg('htmleditor-casimage',Ext.ux.RapidApp.Plugin.HtmlEditor.SimpleCAS_Image);

Ext.ux.RapidApp.Plugin.HtmlEditor.DVSelect = Ext.extend(Ext.util.Observable, {
	
	// This should be defined in consuming class
	dataview: { xtype: 'panel', 	html: '' },
	
	// This should be defined in consuming class
	getInsertStr: function(Records) {},
	
	title: 'Select Item',
	height: 400,
	width: 500,
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
		
		if(Ext.isIE) {
			// Need to do this in IE because if the user tries to insert an image before the editor
			// is "activated" it will go no place. Unlike FF, in IE the only way to get it activated
			// is to click in it. The editor will automatically enable its toolbar buttons again when
			// its activated.
			this.cmp.on('afterrender',this.disableToolbarInit, this,{delay:1000, single: true});
		}
	},
	
	disableToolbarInit: function() {
		if(!this.cmp.activated) {
			this.cmp.getToolbar().disable();
		}
	},
	
	onRender: function() {
		
		this.btn = this.cmp.getToolbar().addButton({
				iconCls: 'x-edit-image',
				handler: this.loadDVSelect,
				text: this.title,
				scope: this
				//tooltip: {
				//	title: this.langTitle
				//},
				//overflowText: this.langTitle
		});
	},
	
	insertContent: function(str) {
		if(!this.cmp.activated) {
			// This works in FF, but not in IE:
			this.cmp.onFirstFocus();
		}
		this.cmp.insertAtCursor(str);

	},
	
	loadDVSelect: function() {
		
		if (this.dataview_enc) { this.dataview = Ext.decode(this.dataview_enc); }
		
		this.dataview.itemId = 'dv';
		
		this.win = new Ext.Window({
			title: this.title,
			layout: 'fit',
			width: this.width,
			height: this.height,
			closable: true,
			modal: true,
			items: this.dataview,
			buttons: [
				{
					text : 'Select',
					scope: this,
					handler : function() {
						
						var dv = this.win.getComponent('dv');
						
						var recs = dv.getSelectedRecords();
						if (recs.length == 0) { return; }
						
						var str = this.getInsertStr(recs);
						this.win.close();
						return this.insertContent(str);
					}
				},
				{
					text : 'Cancel',
					scope: this,
					handler : function() {
						this.win.close();
					}
				}
			]
		});
		
		this.win.show();
	}
});
Ext.preg('htmleditor-dvselect',Ext.ux.RapidApp.Plugin.HtmlEditor.DVSelect);

Ext.ux.RapidApp.Plugin.HtmlEditor.AutoSizers = Ext.extend(Ext.util.Observable, {
	// private
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
		this.cmp.on('push', this.onPush, this);
		this.cmp.autoHeightUp = function() {
			var editorBody = this.getEditorBody();
			if (editorBody.scrollHeight > editorBody.clientHeight) {

				var tbheight = this.getToolbar().getHeight();
				var height = editorBody.scrollHeight + tbheight + 5;
				if (height < this.initialConfig.height) {
					height = this.initialConfig.height;
				}
				if(this.Resizer) {
					this.Resizer.resizeTo(this.getWidth(),height);
				}
				else {
					this.setHeight(height);
					this.wrap.setHeight(height);
				}
			}
		};
		this.cmp.setMinHeight = function() {
			var height = this.initialConfig.minHeight || 150;
			if(this.Resizer) {
				this.Resizer.resizeTo(this.getWidth(),height);
			}
			else {
				this.setHeight(height);
				this.wrap.setHeight(height);
			}
		};
	},

	onPush: function() {
		if(this.initExpanded) { return; }
		this.cmp.autoHeightUp();
		this.initExpanded = true;
	},

	// private
	onRender: function(){
		this.cmp.getToolbar().add(
			'->',
			new Ext.ux.RapidApp.BoxToolBtn({
				toolType: 'minimize',
				toolQtip: 'Min Height',
				handler: this.cmp.setMinHeight,
				scope: this.cmp
			}),
			new Ext.ux.RapidApp.BoxToolBtn({
				toolType: 'maximize',
				toolQtip: 'Expand Height',
				handler: this.cmp.autoHeightUp,
				scope: this.cmp
			})
		);
	}
});
Ext.preg('htmleditor-autosizers',Ext.ux.RapidApp.Plugin.HtmlEditor.AutoSizers);


Ext.ux.RapidApp.Plugin.HtmlEditor.LoadHtmlFile = Ext.extend(Ext.util.Observable, {
	
	title: 'Load from File',
	height: 400,
	width: 500,
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
	},
	
	onRender: function() {
		var itm = {
			iconCls: 'ra-icon-paste-document',
			handler: this.selectHtmlFile,
			text: this.title,
			scope: this
		};
		if(this.cmp.ToolsMenu){
			this.cmp.ToolsMenu.add(itm);
		}
		else {
			this.btn = this.cmp.getToolbar().addButton(itm);
		}
	},
	
	replaceContent: function(str) {
		if(!this.cmp.activated) {
			// This works in FF, but not in IE:
			this.cmp.onFirstFocus();
		}
		this.cmp.setValue(str);
	},
	
	selectHtmlFile: function() {
		var upload_field = {
			xtype: 'fileuploadfield',
			emptyText: 'Select image or html/mht file',
			name: 'Filedata',
			buttonText: 'Browse',
			hideLabel: true,
			anchor: '100%'
		};
		
		var fieldset = {
			style: 'border: none',
			hideBorders: true,
			xtype: 'fieldset',
			border: false,
			items:[ upload_field ]
		};
		
		var callback = function(form,res) {
			var packet = Ext.decode(res.response.responseText);
			this.replaceContent(packet.content);
		};
		
		Ext.ux.RapidApp.WinFormPost.call(this,{
			title: 'Load from File (replace existing content)',
			width: 430,
			height:140,
			url:'/simplecas/texttranscode/transcode_html',
			useSubmit: true,
			fileUpload: true,
			fieldset: fieldset,
			success: callback
			//failure: callback
		});
	}
});
Ext.preg('htmleditor-loadhtml',Ext.ux.RapidApp.Plugin.HtmlEditor.LoadHtmlFile);


Ext.ux.RapidApp.Plugin.HtmlEditor.SaveMhtml = Ext.extend(Ext.util.Observable, {
	
	title: 'Download as File',
	iconCls: 'ra-icon-document-save',
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
	},
	
	onRender: function() {
		var itm = {
			text: this.title,
			iconCls: this.iconCls,
			handler: this.downloadFile,
			scope: this
		};
		if(this.cmp.ToolsMenu){
			this.cmp.ToolsMenu.add(itm);
		}
		else {
			this.btn = this.cmp.getToolbar().addButton(itm);
		}
	},
	
	downloadFile: function() {
		var html = this.cmp.getRawValue();

		//Ext.ux.iframeBgDownload(
		//Ext.ux.postwith('/simplecas/texttranscode/generate_mhtml_download',{
		Ext.ux.iFramePostwith('/simplecas/texttranscode/generate_mhtml_download',{
			html_enc: Ext.encode({ data: html })
		});
	}
});
Ext.preg('htmleditor-save-mhtml',Ext.ux.RapidApp.Plugin.HtmlEditor.SaveMhtml);



Ext.ux.RapidApp.Plugin.HtmlEditor.LoadHelp = Ext.extend(Ext.util.Observable, {
	
	title: 'Load & Download HowTo',
	height: 430,
	width: 675,
	
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
	},
	
	onRender: function() {
		var itm = {
			iconCls: 'ra-icon-help',
			handler: this.showWindow,
			text: this.title,
			scope: this
		};
		if(this.cmp.ToolsMenu){
			this.cmp.ToolsMenu.add(itm);
		}
		else {
			this.btn = this.cmp.getToolbar().addButton(itm);
		}
	},
	
	showWindow: function(){
		Ext.ux.RapidApp.showIframeWindow({
			title: this.title,
			height: this.height,
			width: this.width,
			src: '/assets/rapidapp/misc/static/html/htmleditor_load_download_help.html'
		});
	}
	
});
Ext.preg('htmleditor-loadhelp',Ext.ux.RapidApp.Plugin.HtmlEditor.LoadHelp);





Ext.ux.RapidApp.Plugin.HtmlEditor.InsertFile = Ext.extend(Ext.util.Observable, {
	
	title: 'Attach File (Link)',
	height: 400,
	width: 500,
	
	constructor: function(cnf) {
		Ext.apply(this,cnf);
	},
	
	init: function(cmp){
		this.cmp = cmp;
		this.cmp.on('render', this.onRender, this);
		
		var getDocMarkup_orig = this.cmp.getDocMarkup;
		this.cmp.getDocMarkup = function() {
			return '<link rel="stylesheet" type="text/css" href="/assets/rapidapp/filelink/current/filelink.css" />' +
				getDocMarkup_orig.apply(this,arguments);
		}
	},
	
	onRender: function() {
		this.btn = this.cmp.getToolbar().addButton({
				iconCls: 'ra-icon-page-white-zip-link',
				handler: this.selectFile,
				text: this.title,
				scope: this
				//tooltip: {
				//	title: this.langTitle
				//},
				//overflowText: this.langTitle
		});
	},
	
	insertContent: function(str) {
		if(!this.cmp.activated) {
			// This works in FF, but not in IE:
			this.cmp.onFirstFocus();
		}
		this.cmp.insertAtCursor(str);
	},
	
	selectFile: function() {
		var upload_field = {
			xtype: 'fileuploadfield',
			emptyText: 'Select file',
			fieldLabel:'Select File',
			name: 'Filedata',
			buttonText: 'Browse',
			width: 300
		};
		
		var fieldset = {
			style: 'border: none',
			hideBorders: true,
			xtype: 'fieldset',
			labelWidth: 70,
			border: false,
			items:[ upload_field ]
		};
		
		var callback = function(form,res) {
			var packet = Ext.decode(res.response.responseText);
			var url = '/simplecas/fetch_content/' + packet.checksum + '/' + packet.filename;
			var link = '<a class="' + packet.css_class + '" href="' + url + '">' + packet.filename + '</a>';
			this.insertContent(link);
		};
		
		Ext.ux.RapidApp.WinFormPost.call(this,{
			title: 'Insert file',
			width: 430,
			height:140,
			url:'/simplecas/upload_file',
			useSubmit: true,
			fileUpload: true,
			fieldset: fieldset,
			success: callback
		});
	}
});
Ext.preg('htmleditor-insertfile',Ext.ux.RapidApp.Plugin.HtmlEditor.InsertFile);


Ext.ns('Ext.ux.RapidApp.Plugin.HtmlEditor');
Ext.ux.RapidApp.HtmlEditor = Ext.extend(Ext.form.HtmlEditor,{
	initComponent: function() {
		var plugins = this.plugins || [];
		if(!Ext.isArray(plugins)) { plugins = [ this.plugins ]; }
		
    if(! this.no_autosizers){
      plugins.push('htmleditor-autosizers');
    }
    
		plugins.push(
			new Ext.ux.form.HtmlEditor.Break(),
			'htmleditor-toolsmenu',
			'htmleditor-loadhtml',
			'htmleditor-save-mhtml',
			'htmleditor-loadhelp',
			'htmleditor-insertfile',
			{
				ptype: 'htmleditor-casimage',
				maxImageWidth: 800,
				resizeWarn: true
			},
			new Ext.ux.form.HtmlEditor.SpecialCharacters(),
			new Ext.ux.form.HtmlEditor.UndoRedo(),
			new Ext.ux.form.HtmlEditor.Divider(),
			new Ext.ux.form.HtmlEditor.Table(),
			new Ext.ux.form.HtmlEditor.IndentOutdent(),
			new Ext.ux.form.HtmlEditor.SubSuperScript(),
			'clickablelinks'
		);
		this.plugins = plugins;
			
		if(this.resizable) {
			this.on('initialize',function(){
				var Field = this;
				var minHeight = this.minHeight || 50;
				var minWidth = this.minWidth || 100;
				
				this.Resizer = new Ext.Resizable(this.wrap, {
					minHeight: minHeight,
					minWidth: minWidth,
					pinned: true,
					handles: 'se',
					//handles: 's,e,se',
					//dynamic: true,
					listeners : {
						'resize' : function(resizable,width,height) {
							//height = height - 6; //<-- adjust for size of resizer (needed when handles: 's')
							Field.setSize(width,height);
						}
					}
				});
				// Manually fire resize to trigger init adjustment for resizer
				//var size = this.wrap.getSize();
				//resizer.resizeTo(size.width,size.height);
			},this);
		}
			
		Ext.ux.RapidApp.HtmlEditor.superclass.initComponent.call(this);
	}
});
Ext.reg('ra-htmleditor',Ext.ux.RapidApp.HtmlEditor);


/* http://noteslog.com/post/crc32-for-javascript/
=============================================================================== 
Crc32 is a JavaScript function for computing the CRC32 of a string 
............................................................................... 
 
Version: 1.2 - 2006/11 - http://noteslog.com/category/javascript/ 
 
------------------------------------------------------------------------------- 
Copyright (c) 2006 Andrea Ercolino 
http://www.opensource.org/licenses/mit-license.php 
=============================================================================== 
*/ 
(function() { 
    var table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D";     
 
    /* Number */ 
    crc32 = function( /* String */ str, /* Number */ crc ) { 
        if( crc == window.undefined ) crc = 0; 
        var n = 0; //a number between 0 and 255 
        var x = 0; //an hex number 
 
        crc = crc ^ (-1); 
        for( var i = 0, iTop = str.length; i < iTop; i++ ) { 
            n = ( crc ^ str.charCodeAt( i ) ) & 0xFF; 
            x = "0x" + table.substr( n * 9, 8 ); 
            crc = ( crc >>> 8 ) ^ x; 
        } 
        return crc ^ (-1); 
    }; 
})();
/*
=============================================================================== 
*/

Ext.ns('Ext.ux.RapidApp.AppTab');

Ext.ux.RapidApp.AppTab.TabPanel = Ext.extend(Ext.TabPanel, {
	
	itemId: 'load-target',

	layoutOnTabChange: true,
	enableTabScroll: true,
	useContextMenu: true,
	
	applyActiveTab: function(tp,tab) {
		if(this.id == 'main-load-target'){
			var tab = tab || this.getActiveTab();
			
			if(tab) {
				// disabled unfished 'tabPath' feature
				//var load = tab.tabPath || tab.autoLoad;
				var load = tab.autoLoad;
				Ext.ux.RapidApp.HashNav.setHashpath(load);
			}
      else {
        // Only happens when all tabs are closed (which means
        // there is no dashboard - or it has been made closable)
        Ext.ux.RapidApp.HashNav.clearHashpath();
      }
			
			var title = tab ? tab.title : null;
			Ext.ux.RapidApp.HashNav.updateTitle(title);
		}
	},

	initComponent: function() {
		
		// init tab checksum (crc) map:
		this.tabCrcMap = {};
		
		if(this.initLoadTabs) {
			this.on('afterrender',function() {
				Ext.each(this.initLoadTabs,function(cnf) {
					this.loadTab(cnf);
				},this);
			},this);
		}
		
		this.addEvents( 'navload' );
		
		if(this.useContextMenu) {
			this.on('contextmenu',this.onContextmenu,this);
		}
		
		// ------------------------------------------------------------
		// -- special HashNav behaviors if this is the main-load-target
		if(this.id == 'main-load-target'){
			// Handle direct nav on first load: (See Ext.ux.RapidApp.HashNav in History.js)
			this.on('afterrender',function(){
				
				var hash = Ext.ux.RapidApp.HashNav.INIT_LOCATION_HASH;
				if(hash && hash.search('#!/') == 0){
					Ext.ux.RapidApp.HashNav.handleHashChange(hash);
				}
				else {
					this.applyActiveTab();
				}
				
				this.on('tabchange',this.applyActiveTab,this);

			},this);
		}
		// --
		// ------------------------------------------------------------
		
		Ext.ux.RapidApp.AppTab.TabPanel.superclass.initComponent.call(this);
	},
	
	getContextMenuItems: function(tp,tab) {
		var items = [];
		
		var close_item = {
			itemId: 'close_item',
			text: 'Close Other Tabs',
			iconCls: 'ra-icon-tabs-delete',
			scope: tp,
			handler: tp.closeAll.createDelegate(tp,[tab]),
			hideShow: function(){
				if (this.itemId != 'close_item') {
					// Whoever called us was supposed to set the scope to the
					// close_item but didn't
					return;
				}
				this.setVisible(tp.items.getCount() >= 2);
			}
		};
    
    var reload_item = Ext.isFunction(tab.reload) ? {
			itemId: 'reload_item',
			text: 'Reload',
			iconCls: 'ra-icon-refresh',
			scope: tp,
			handler: tab.reload.createDelegate(tab)
		} : null;

		var open_item = tab.loadContentCnf ? {
			itemId: 'open_item',
			text: 'Open in a New Tab',
			iconCls: 'ra-icon-tab-go',
			scope: tp,
			handler: tp.openAnother.createDelegate(tp,[tab])
		} : null;
		
    if(reload_item) { items.push(reload_item); }
		if(close_item)	{ items.push(close_item); }
		if(open_item)	{ items.push(open_item); }
    
		
		// -- New: Optionally get additional menu items defined in the tab itself:
		if(Ext.isFunction(tab.getTabContextMenuItems)) {
			var tabitems = tab.getTabContextMenuItems.call(tab,tp);
			if(Ext.isArray(tabitems)) {
				var newitems = items.concat(tabitems);
				items = newitems;
			}
		}
		// --
		
		return items;
	},
	
	onContextmenu: function(tp,tab,e) {
		// stop browser menu event to prevent browser right-click context menu
		// from opening:
		e.stopEvent();
		
		var items = this.getContextMenuItems(tp,tab);
		if(items.length == 0) { return; }
		
		var menuItems = [];
		Ext.each(items,function(item){
			if(tp.items.getCount() < 2 && item.itemId == 'close_item') {
				return;
			}
			if(item.itemId == 'open_item') { 
				item.text = 'Open Another <b>' + tab.title + '</b>';
        if(!tab.closable) { return; }
			}
			menuItems.push(item);
		},this);
		
    if(menuItems.length > 1) {
      menuItems.splice(1,0,'-');
    }
    
		// Make sure the tab is activated so it is clear which is the Tab that
		// will *not* be closed
		tp.activate(tab);

		var menu = new Ext.menu.Menu({ items: menuItems });
		var pos = e.getXY();
		pos[0] = pos[0] + 10;
		pos[1] = pos[1] + 5;
		menu.showAt(pos);
	},
	
	closeAll: function(tab) {
		this.items.each(function(item) {
			if (item.closable && item != tab) {
				this.remove(item);
			}
		},this);
	},
	
	openAnother: function(tab) {
		var cnf = Ext.apply({},tab.loadContentCnf);
		if(cnf.id) { delete cnf.id; }
		if(cnf.itemId) { delete cnf.itemId; }
		cnf.newtab = true;
		this.loadTab(cnf);
	},

	
	
	// "navsource" property is meant to be used to store a reference to the navsource
	// container (i.e. AppTree) that calls "loadContent". This needs to be set by the
	// navsource itself
	navsource: null,
	setNavsource: function(cmp) {
		this.navsource = cmp;
	},
	getNavsource: function() {
		return this.navsource;
	},

	loadContent: function() {
		this.fireEvent( 'navload' );
		return this.loadTab.apply(this,arguments);
	},

	loadTab: function(cnfo,extra_cnf) {
		
		// Clone to protect original references:
		var enc_orig_cnf = Ext.encode(cnfo);
		var cnf = Ext.decode(enc_orig_cnf);
		var orig_cnf = Ext.decode(enc_orig_cnf);
		
		if(cnf.newtab) { //<-- the newtab param is set by the "open another tab" plugin
			delete cnf.newtab;
			cnf.seq = cnf.seq || 0;
			cnf.seq++;
			cnf.autoLoad = cnf.autoLoad || {};
			cnf.autoLoad.params = cnf.autoLoad.params || {};
			cnf.autoLoad.params['_seq'] = cnf.seq.toString();
		}
		
		// -- New: apply optional second cnf argument:
		if(Ext.isObject(extra_cnf)) {
			Ext.apply(cnf,extra_cnf);
		}
		// --
		
		cnf = Ext.apply({
			loadContentCnf: orig_cnf, //<-- save the cnf used
			xtype: 'autopanel',
			//itemId: 'tab-' + Math.floor(Math.random()*100000),
			layout: 'fit',
			closable: true,
			title: 'Loading',
			iconCls: 'ra-icon-loading',
			autoLoad: {}
		},cnf);
			
		Ext.applyIf(cnf.autoLoad, {
			text: 'Loading...',
			nocache: true,
			params: {}
		});
		
		cnf.autoLoad.url = cnf.autoLoad.url || cnf.url;
		Ext.apply(cnf.autoLoad.params,cnf.params||{});
		
		// ------------------------
		// Attempt to sanitize special characters in the URL. Only do this for absolute
		// URL paths *relative* to the current hostname (i.e. start with '/'). This is
		// needed for REST paths, specifically situations where the db key contains funky
		// characters.
		// TODO: the right way to fix this is on the backend by encoding the REST key
		if(cnf.autoLoad.url.search('/') == 0) {
			// The following 3 lines are a roundabout way to encode all special characters
			// except '/'. Manually encode '.' because it isn't considered 'special' by the
			// encodeURIComponent function
			var encUrl = encodeURIComponent(cnf.autoLoad.url);
			var encUrl2 = encUrl.replace(/\%2F/g,'/');
			cnf.autoLoad.url = encUrl2.replace(/\./g,'%2E');
		}
		// ------------------------
		
		// ------------------------
		// Generate a checksum (using a crc algorithm) of the
		// *actual* url/params of the target. This allows dynamically checking
		// if a supplied loadContent is already open (see existTab below)
		//var tabCrc = 'tab-crc' + crc32(Ext.encode(
		//	[cnf.autoLoad.url,cnf.autoLoad.params]
		//));
		var tabCrc = this.getLoadCrc(cnf.autoLoad);
		
		// Check if this Tab is already loaded, and set active and return if it is:
		var existTab = this.getComponent(this.tabCrcMap[tabCrc]) || 
			this.getComponent(cnf.id) || this.getComponent(cnf.itemId);
		if (existTab) {
			//console.dir(existTab);
			return this.activate(existTab);
		}
		// ------------------------
		
		var tp = this;
		
		if(!cnf.cmpListeners) { cnf.cmpListeners = {}; }
		if(!cnf.cmpListeners.beforerender) { cnf.cmpListeners.beforerender = Ext.emptyFn; }
		cnf.cmpListeners.beforerender = Ext.createInterceptor(
			cnf.cmpListeners.beforerender,
			function() {
				var tab = this.ownerCt;
				
				// optional override if supplied in cnf:
				var setTitle = cnf.tabTitle || this.tabTitle;
				var setIconCls = cnf.tabIconCls || this.tabIconCls;
        var setTitleCls = cnf.tabTitleCls || this.tabTitleCls;
				
				if(!setIconCls && tab.iconCls == 'ra-icon-loading') {
					setIconCls = 'ra-icon-page';
				}
				
				if(!setTitle && tab.title == 'Loading') {
					var max_len = 10;
					var url_st = cnf.autoLoad.url.split('').reverse().join('');
					var str = url_st;
					if(url_st.length > max_len) { 
						str = url_st.substring(0,max_len) + '...'; 
					}
					setTitle = 'Untitled (' + decodeURIComponent(str.split('').reverse().join('')) + ')';
				}
				
        if(setTitle && setTitleCls) {
          setTitle = '<span class="' + setTitleCls + '">' + setTitle + '</span>';
        }
				if(setTitle) { tab.setTitle(setTitle); }
				if(setIconCls) { tab.setIconClass(setIconCls); }
				
				/* 'tabPath' - unfinished feature
				if(this.tabPath) {
					tab.tabPath = this.tabPath;
					var tabId = tab.itemId || tab.getId();
					var Crc = tp.getLoadCrc(tab.tabPath);
					if(Crc) {
						tp.tabCrcMap[Crc] = tabId;
					}
				}
				*/
				
				tp.applyActiveTab.call(tp);
			}
		);
		
		var new_tab = this.add(cnf);
		var tabId = new_tab.itemId || new_tab.getId();
		if(tabCrc) { 
			// Map the crc checksum to the id of the tab for lookup later (above)
			this.tabCrcMap[tabCrc] = tabId;
		}
		
		this.activate(new_tab);
    return new_tab;
	},
	
	getLoadCrc: function(load) {
		if(Ext.isString(load) || Ext.isObject(load)) {
			var autoLoad = Ext.isString(load) ? {url:load,params:{}} : load;
			return 'tab-crc' + crc32(Ext.encode(
				[decodeURIComponent(autoLoad.url),autoLoad.params]
			));
		}
		return null;
	},
	
	closeActive: function() {
		var activePanel = this.getActiveTab();
		this.remove(activePanel);
	}
	
});
Ext.reg('apptabpanel', Ext.ux.RapidApp.AppTab.TabPanel);

// This is designed to be a function that can be supplied to a treepanel 
// click handler. This assumes the node has a compatible 'loadContentCnf'
// attribute and that the tree has a reference to a compatable 'loadTargetObj'
// (defined as a property). 
Ext.ux.RapidApp.AppTab.treenav_click = function(node,event) {
	var tree = node.getOwnerTree();
	var loadTarget = tree.loadTargetObj;
	
	// Update the loadTarget with a refernece back to us. This is needed in case
	// an app needs to tell us to reload (such as in the case of saving AppGrid2 searches
	loadTarget.setNavsource(tree);
	
	// Do nothing if the node has no loadContentCnf
	if (! node.attributes.loadContentCnf) { return; }
	
	return loadTarget.loadContent(node.attributes.loadContentCnf);
}


Ext.ux.RapidApp.AppTab.findParent_loadTarget = function(cnt) {
	var loadTarget = null;
	var parent = cnt.findParentBy(function(cmp) {
		if (!cmp.getComponent) { return false;} 
		loadTarget = cmp.getComponent('load-target');
		if(loadTarget) { return true; }
		return false;
	});
	return loadTarget;
};

Ext.ux.RapidApp.AppTab.cnt_init_loadTarget = function(cnt) {
	cnt.loadTargetObj = Ext.ux.RapidApp.AppTab.findParent_loadTarget(cnt);
	// If a lodTarget wasn't found above, ball back to the global id:
	if(!cnt.loadTargetObj) {
		cnt.loadTargetObj = Ext.getCmp('main-load-target');
	}
}



/*
Ext.ux.RapidApp.AppTab.cnt_init_loadTarget = function(cnt) {
	var loadTarget;
	var parent = cnt.findParentBy(function(cmp) {
		loadTarget = cmp.getComponent('load-target');
		if(loadTarget) { return true; }
		return false;
	});
	cnt.loadTargetObj = loadTarget;
	//tree.loadTargetObj = tree.ownerCt.ownerCt.getComponent('load-target');
}
*/

Ext.ux.RapidApp.AppTab.gridrow_nav = function(grid,rec) {
  // Support argument as either index or actual Record:
  var Record = Ext.isNumber(rec) ? grid.store.getAt(rec) : rec;

  // -- NEW: ignore phantom records (Github Issue #26)
  if(Record.phantom) { return; }
  // --

  // --- NEW: try to use REST nav (Github Issue #34)
  if(grid.open_record_via_rest && grid.open_record_url) {
    var key = grid.open_record_rest_key ? grid.open_record_rest_key : 'id';
    var val = grid.open_record_rest_key ? Record.data[key] : Record.id;
    
    if(!val) { throw 'gridrow_nav/REST open: failed to identify Record value!'; }

    //var hashpath = '#!' + this.open_record_url + '/' + key + '/' + val;
    var hashpath = '#!' + grid.open_record_url + '/' + val;
    window.location.hash = hashpath;
  }
  // ---
  else {
    // Original, pre-REST design
    var loadTarget = grid.loadTargetObj;
    return Ext.ux.RapidApp.AppTab.tryLoadTargetRecord(loadTarget,Record,grid);
  }
}



Ext.ux.RapidApp.AppTab.tryLoadTargetRecord = function(loadTarget,Record,cmp) {
	if(!loadTarget) { return; }
	var orig_params = Ext.apply({},Record.data);
	if(orig_params.loadContentCnf) {
		var loadCfg = Ext.decode(orig_params.loadContentCnf);	
		delete orig_params.loadContentCnf;
		
		if(cmp && cmp.filteredRecordData) {
			orig_params = cmp.filteredRecordData(orig_params);
		}
	
		if (!loadCfg.params) { loadCfg.params = {}; }
		Ext.apply(loadCfg.params,{ orig_params: Ext.encode(orig_params) });
		
		return loadTarget.loadContent(loadCfg);
	}
}




Ext.ux.RapidApp.AppTab.AppGrid2Def = {
	
	// Override Ext.Component.getId() auto id generation
	getId : function(){
		return this.id || (this.id = 'appgrid-' + (++Ext.Component.AUTO_ID));
	},
	
	viewConfig: {
		emptyText: '<div style="font-size:16px;color:#d0d0d0;padding-top:10px;padding-left:25px">' +
			'(No Data)</div>',
		
		// -- http://www.sencha.com/learn/legacy/Ext_FAQ_Grid#Maintain_GridPanel_scroll_position_across_Store_reloads
		onLoad: Ext.emptyFn,
		listeners: {
			beforerefresh: function(v) {
				v.scrollTop = v.scroller.dom.scrollTop;
				v.scrollHeight = v.scroller.dom.scrollHeight;
			},
			refresh: function(v) {
				v.scroller.dom.scrollTop = v.scrollTop + 
				(v.scrollTop == 0 ? 0 : v.scroller.dom.scrollHeight - v.scrollHeight);
			},
      rowsinserted: function(v,fNdx,lNdx) {
        // Scroll new/added rows into view (Github Issue #19):
        v.focusCell(lNdx,0);
        var sm = v.grid.getSelectionModel();
        // Select/highlight the new rows:
        sm.selectRange(fNdx,lNdx);
      }
		}
		// --
	},
	
	getOptionsMenu: function() {
		return Ext.getCmp(this.options_menu_id);
	},

	filteredRecordData: function(data) {
		// Return data as-is if primary_columns is not set:
		if(! Ext.isArray(this.primary_columns) ) { return data; }
		// Return a new object filtered to keys of primary_columns
		return Ext.copyTo({},data,this.primary_columns);
	},
	
	saveStateProperties: [
		'filterdata', 						// MultiFilters
		'filterdata_frozen', 			// Frozen MultiFilters
		'column_summaries',				// Column Summaries
		'quickSearchCheckIndexes',		// Quick Search checked columns
		'open_record_column_hidden',	// Hidden state of special open record column
		'advanced_config',
		'advanced_config_json',
		'advanced_config_active',
		'quicksearch_mode',
    'custom_headers',
    'disable_cell_editing'
	],
	
	// Function to get the current grid state needed to save a search
	// TODO: factor to use Built-in ExtJS "state machine"
	getCurSearchData: function () {
		var grid = this;
		var colModel = grid.getColumnModel();
						
		var columns = {};
		var column_order = [];
		Ext.each(colModel.config,function(item) {
			if (item.name) {
				columns[item.name] = Ext.copyTo({},item,grid.column_allow_save_properties);
				column_order.push(item.name);
			}
		},this);
		
		// view_config gets saved into 'saved_state' when a search is saved:
		var view_config = {
			columns: columns,
			column_order: column_order,
			sort: grid.getState().sort || null
		};

		var store = grid.getStore();
		
		
		
		/*
		//MultiFilter data:
		if(store.filterdata) { view_config.filterdata = store.filterdata; }
		if(store.filterdata_frozen) { view_config.filterdata = store.filterdata; }
		
		//GridSummary data
		if(store.column_summaries) { view_config.column_summaries = store.column_summaries; }
		*/
		
		var bbar = grid.getBottomToolbar();
		view_config.pageSize = bbar ? bbar.pageSize : (this.pageSize || null);
		
		// Copy designated extra properties to be saved into view_config (saved_state):
		Ext.copyTo(
			view_config, store,
			grid.saveStateProperties
		);
		
		return view_config;
	},
	
	storeReloadButton: false,
	titleCount: false,
	
	/*
     NEW: 'ADVANCED_CONFIG' OVERRIDE 
     
     Optionally loads any additonal config parameters from the special config 
     property 'advanced_config' if 'advanced_config_active' is also present and
     set to truthy value.
     
     This is here primarily for saved searches. Note: this is a very powerful,
     and thus possibly dangerous functionality. But the risk is at the point where
     this config property is loaded.
	*/
	initAdvancedConfig: function(){
		// Want this to be a global override to Ext.Component. But that causes 
		// loading order issues. Want to find a way to apply the config to the 
		// original config supplied to the constructor...  Need to figure out a
		// way to do that
		
		// If there is a store within this component, optionally copy the 
		// advanced_config properties from it:
		if(this.store) {
			Ext.apply(this,Ext.copyTo({},this.store,[
				'advanced_config',
				'advanced_config_active',
				'advanced_config_json'
			]));
		}
		
		// Optionally load from JSON string:
		if(this.advanced_config_active && this.advanced_config_json) {
			this.advanced_config = Ext.decode(this.advanced_config_json);
		}
		
		if(this.advanced_config_active && this.advanced_config) {
			Ext.apply(this,this.advanced_config);
		}
	
	},

	initComponent: function() {
	
		this.initAdvancedConfig();
	
		//console.dir([this,this.initialConfig]);
		
		if(this.force_read_only && this.store.api) {
			this.store.api.create = null;
			this.store.api.update = null;
			this.store.api.destroy = null;
		}
	
		this.addEvents('firstload');
		
		this.on('afterrender',this.addExtraToOptionsMenu,this);
		
		this.store.on('beforeload',this.reloadColumns,this);

		// -- Force default sort to be DESC instead of ASC:
		var orig_store_singleSort = this.store.singleSort;
		this.store.singleSort = function(field,dir) {
			if(!dir && (!this.sortInfo || this.sortInfo.field != field)) {
				if(!this.sortToggle || !this.sortToggle[field]) { 
					this.sortToggle[field] = 'ASC';
					this.sortInfo = {
						field: field,
						direction: 'ASC'
					};
				}
				arguments[1] = this.sortToggle[field].toggle('ASC','DESC');
			}
			orig_store_singleSort.apply(this,arguments);
		}
		// --
		
		// Check to make sure store_autoLoad has not been set to a false value in
		// either the store or the grid config (which is now allowed to override the
		// store setting, see datastore-plus code)
		var store_autoLoad_disabled = (
			(typeof this.store_autoLoad != 'undefined' && !this.store_autoLoad) || 
			!this.store.store_autoLoad
		) ? true : false;
		
		// -- Workaround - manual single-use loadMask for the very first load
		// Need to investigate more why this is needed, and why the 'loadMask' grid
		// setting doesn't work on the first store load. I think it is related to
		// load order and possibly autoPanel. 
		// TODO: generalize/move this into datastore-plus
		if(!this.collapsed && !store_autoLoad_disabled) {
			this.on('afterrender',function() {
				var lMask = new Ext.LoadMask(this.getEl(),{ msg: "Loading Data Set" });
				lMask.show();
				var hide_fn;
				hide_fn = function(){ 
					this.fireEvent('firstload');
					lMask.hide(); 
					this.store.un('load',hide_fn);
					this.store.un('exception',hide_fn); 
				};
				this.store.on('load',hide_fn,this);
				this.store.on('exception',hide_fn,this);
			},this);
		}
		// --
		
		
		if(this.storeReloadButton) {
			this.tools = [{
				id: 'refresh',
				handler: function() {
					this.getStore().reload();
				},
				scope: this
			}]
		}
		
		// If the store has pageSize set then it came from a saved search and
		// we use it:
		if (this.store.pageSize) { this.pageSize = this.store.pageSize; }
		
		// -- vv -- 
		// Enable Ext.ux.RapidApp.Plugin.GridHmenuColumnsToggle plugin:
		if(!this.plugins){ this.plugins = []; }
		this.plugins.push('grid-hmenu-columns-toggle');
		// -- ^^ --
		
		if(this.use_column_summaries) { this.plugins.push('appgrid-summary'); }
		if(this.use_autosize_columns || this.auto_autosize_columns) { 
			this.plugins.push('appgrid-auto-colwidth'); 
		}
		
		// Toggle All:
		this.plugins.push('appgrid-toggle-all-cols');
		this.plugins.push('appgrid-filter-cols');
		
		this.plugins.push('appgrid-batch-edit'); 
		
		// remove columns with 'no_column' set to true:
		var new_columns = [];
		var num_not_hidden_cols = 0;
		Ext.each(this.columns,function(column,index,arr) {
			if(!column.no_column) {
				if(!column.hidden) { num_not_hidden_cols++; }
				
				// check for special 'allow_edit' attribute:
				if(typeof column.allow_edit != "undefined" && !column.allow_edit) { 
					if(!column.allow_batchedit) {
						column.editable = false;
					}
				}
				
				// autoExpandColumn feature relies on the "id" property. Here we set it
				// automatically to be the same as the column name.
				if(this.autoExpandColumn && this.autoExpandColumn == column.name) {
					column.id = column.name;
				}
				
				if(column.summary_functions) { column.summaryType = 'dummy'; }
        
        if(this.store.custom_headers && this.store.custom_headers[column.name]) {
          column.header = this.store.custom_headers[column.name];
        }
				
				new_columns.push(column);
			}
		},this);
		this.columns = new_columns;
		
		// -- If every single column is hidden, the the hmenu won't be available. Override
		// the hidden setting on only the very first column in this case:
		if(num_not_hidden_cols == 0 && this.columns.length > 0) {
			this.columns[0].hidden = false;
		}
		// --
    
    var tbar_items = [];
    if(Ext.isArray(this.tbar)) { tbar_items = this.tbar; }
    
    this.tbar = {
      xtype: 'toolbar',
      // TODO: enable overflow on top toolbar. 
      //   The Quick Search box will need to be adapted 
      //   to work in the overflow menu before this can be set
      //enableOverflow: true,
      items: tbar_items
    };
		
		var bbar_items = [];
		if(Ext.isArray(this.bbar)) { bbar_items = this.bbar; }
		
		// Override for consistency: push buttons to the right to match general positioning
		// when the paging toolbar is active
		if(this.force_disable_paging) { bbar_items.push('->'); }
		
		this.bbar = {
			xtype:	'toolbar',
			items: bbar_items
		};
		
		if(this.pageSize && !this.force_disable_paging) {
			Ext.apply(this.bbar,{
				xtype:	'rapidapp-paging',
				store: this.store,
				pageSize: this.pageSize,
				displayInfo : true,
				//prependButtons: true,
				items: bbar_items
			});
			if(this.maxPageSize) { this.bbar.maxPageSize = this.maxPageSize; }
		}
		
		// ----- MultiFilters: ----- //
		if (this.use_multifilters) {
			if(!this.plugins){ this.plugins = []; }
			this.plugins.push(new Ext.ux.MultiFilter.Plugin);
		}
		// ------------------------- //
		
		// ----- Clear Sort: ----- //
		this.plugins.push('grid-hmenu-clear-sort');
		// ----------------------- //
		
		
		// ------ Grid Quick Search --------- //
		if (this.gridsearch && this.tbar) {

			var grid_search_cnf = {
				//iconCls:'ra-icon-zoom',
				autoFocus:false,
				mode: 'local', // local or remote
				position: 'top'
			};

			if (this.gridsearch_remote) { grid_search_cnf['mode'] = 'remote'; }

			if(!this.plugins){ this.plugins = []; }
			//this.plugins.push(new Ext.ux.grid.Search(grid_search_cnf));
			this.plugins.push(new Ext.ux.RapidApp.Plugin.GridQuickSearch(grid_search_cnf));
		}
		// ---------------------------- //
		

		// ---- Delete support: LEGACY - this code is depricated by DataStorePlus 'destroy'
		if (this.delete_url) {
			this.checkbox_selections = true;
			var storeId = this.store.storeId;
			var deleteBtn = new Ext.Button({
				text: 'delete',
				iconCls: 'ra-icon-bullet-delete',
				handler: function(btn) {
					var grid = btn.ownerCt.ownerCt;
					var Records = grid.getSelectionModel().getSelections();
					var rows = [];
					Ext.each(Records,function(item) {
						rows.push(grid.filteredRecordData(item.data));
					});
					
					// Don't do anything if no records are selected:
					if(rows.length == 0) { return; }

					Ext.ux.RapidApp.confirmDialogCall(
						'Confirm delete', 'Really delete ' + rows.length + ' selected records?',
						function() {
							Ext.Ajax.request({
								url: grid.delete_url,
								params: {
									rows: Ext.encode(rows)
								},
								success: function(response) {
									grid.getStore().reload();
								}
							});
						}
					);
				}
			});
			this.bbar.items.unshift(
				'Selection:',
				deleteBtn,
				'-'
			);
		}
		
		// Remove the bbar if its empty and there is no pageSize set (and there are no store buttons):
		if (this.bbar.items.length == 0 && !this.pageSize && !this.setup_bbar_store_buttons) { 
			delete this.bbar; 
		}
		
		// Optional override to force disable the bbar:
		if(this.force_disable_bbar && this.bbar) { 
			delete this.bbar; 
		}
		
		// Optional override to force disable the tbar:
		if(this.force_disable_tbar && this.tbar) { 
			delete this.tbar; 
		}
		
		this.init_open_record_handler();
		
		if(this.checkbox_selections) {
			this.sm = new Ext.grid.CheckboxSelectionModel();
			this.columns.unshift(this.sm);
		}
    
    // This is duplicated in the 'grid-toggle-edit-cells' plugin but is
    // also added here to honor the setting even if the plugin (i.e. the
    // toggle button) is not loaded/available
    this.on('beforeedit',function(){
       return this.store.disable_cell_editing ? false : true;
    },this);

		Ext.ux.RapidApp.AppTab.AppGrid2.superclass.initComponent.call(this);
	},
	
	onRender: function() {
		
		this.reloadColumnsTask = new Ext.util.DelayedTask(function(){
			this.reloadColumns();
		},this);
		
		this.storeReloadTask = new Ext.util.DelayedTask(function(){
			this.reloadColumns();
			this.store.reload();
		},this);
		
		this.getColumnModel().on('hiddenchange',function(colmodel,colIndex,hidden) {
			
			if(colmodel.config[colIndex] && 
			 colmodel.config[colIndex].dataIndex == '___open_action_col') {
				// Update the store open_record_column_hidden param with the current status 
				// (needed for saved searches):
				this.store.open_record_column_hidden = hidden;
				// Don't reload the store for the open record column:
				return;
			}
			
			// Only reload the store when showing columns that aren't already loaded
			if(hidden || this.loadedColumnIndexes[colIndex]) { 
				// Need to set reloadColumns even if no store reload is needed so
				// that clicking to sort on a column will use the new column data
				// on its request to the store:
				//this.reloadColumns();
				this.reloadColumnsTask.delay(100);
				return; 
			}
			//this.reloadColumnsTask.delay(100);
			//this.reloadColumns(); // <-- this has to be done effectively twice to make sure lastOptions are changed
			
			//store reload task with delay for clicking several columns at once:
			this.storeReloadTask.delay(750); 
		},this);
		
		var store_load_parms = {};
		
		if (this.sort) {
			//Ext.apply(store_load_parms,{
			//	sort: this.sort
			//});
			this.applyState({ sort: this.sort });
		}
		
		if (this.pageSize) {
			Ext.apply(store_load_parms,{
				start: 0,
				limit: parseFloat(this.pageSize)
			});
		}
		
		if(this.store.autoLoad && this.store.autoLoad.params) {
			Ext.apply(this.store.autoLoad.params,store_load_parms);
		}
		// alternate 'store_autoLoad' setting - see DataStore2.pm and datastore-plus plugin:
		else if(this.store.store_autoLoad && this.store.store_autoLoad.params) {
			Ext.apply(this.store.store_autoLoad.params,store_load_parms);
		}
		else {
			this.store.load({ params: store_load_parms });
		}
    
    // -- Hooks to update the edit headers (pencil icon in column headers)
    this.updateEditHeaders();
    this.on('reconfigure',this.updateEditHeaders,this);
    this.getView().on('refresh', this.updateEditHeaders, this);
    if(this.ownerCt) {
      this.ownerCt.on('show',this.updateEditHeaders,this);
    }
    // --
		
		Ext.ux.RapidApp.AppTab.AppGrid2.superclass.onRender.apply(this, arguments);
	},
	
	init_open_record_handler: function() {
		if(this.open_record_url) {
      // Consolidated back into single function for REST or loadTarget
      // (Github Issue #34)
      this.row_open_handler = Ext.ux.RapidApp.AppTab.gridrow_nav;
			
			if(this.open_record_column) {
				// optionally set the hidden status param from the store 
				// (i.e. loaded from saved search)
				this.open_record_column_hidden = 
					(typeof this.store.open_record_column_hidden == 'undefined') ?
						this.open_record_column_hidden : this.store.open_record_column_hidden;
					
				this.columns.unshift({
					xtype: 'actioncolumn',
					width: 30,
					name: '___open_action_col',
					dataIndex: '___open_action_col',
					sortable: false,
					menuDisabled: true,
					resizable: false,
					hidden: this.open_record_column_hidden,
					header: '<span ' +
							'style="padding-left:0px;height:12px;color:#666666;" ' +
							'class="with-icon ra-icon-magnify-tiny"' + 
						'>' +
						// using a bunch of &nbsp; instead of padding-left for IE. Idea is to push the 
						// header text to the right far enough so it can't be seen in the column header,
						// but can still be seen in the columns menu to toggle on/off. The column header
						// appears to show 
						'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
						'<i>Open Item Column</i></span>',
					items: [{
            iconCls: 'ra-icon-magnifier ra-ra-icon-actioncolumn',
						tooltip: 'Open Item',
						handler: this.row_open_handler,
						scope: this
					}]
				});
			}
			
			this.on('rowdblclick',this.row_open_handler,this);
		}
	},
	
	alwaysRequestColumns: {},
	
	currentVisibleColnames: function() {
		var cm = this.getColumnModel();
		
		// Reset loadedColumnIndexes back to none
		this.loadedColumnIndexes = {};
		
		var columns = cm.getColumnsBy(function(c){
			if(this.alwaysRequestColumns[c.name]) { return true; }
			if(
				c.hidden || c.dataIndex == "" || 
				c.dataIndex == '___open_action_col'
			){ 
				return false; 
			}
			return true;
		},this);
		
		var colDataIndexes = [];
		var seen = {};
		Ext.each(columns,function(i) {
			if(!seen[i.dataIndex]) {
				colDataIndexes.push(i.dataIndex);
				seen[i.dataIndex] = true;
			}
			this.loadedColumnIndexes[cm.findColumnIndex(i.dataIndex)] = true;
		},this);
		
		return colDataIndexes;
		
	},
	
	reloadColumns: function(store,opts) {
		if(!store){ store = this.store; }

		var colDataIndexes = this.currentVisibleColnames();
		
		var params = { columns: Ext.encode(colDataIndexes) };
		if(opts && opts.params) {
			Ext.apply(params,opts.params);
		}
		
		if(this.baseParams) {
			Ext.apply(params,this.baseParams);
		}
		
		Ext.apply(store.baseParams,params);
		// Set lastOptions as well so reload() gets the new columns:
		Ext.apply(store.lastOptions.params,params);
	},
	
	// Pulls a copy of the Tab right-click context menu into the Grid Options menu
	addExtraToOptionsMenu: function() {
		if(this.addExtraToOptionsMenuCalled) { return; }
		this.addExtraToOptionsMenuCalled = true;
		
		var optionsMenu = this.getOptionsMenu();
		if(!optionsMenu) { return; }

		var ourTab = this.ownerCt;
		if(!ourTab || !ourTab.loadContentCnf) { return; }
		
		var ourTp = ourTab.ownerCt;
		if(!ourTp || !Ext.isFunction(ourTp.getContextMenuItems)) { return; }
		
		var contextItems = ourTp.getContextMenuItems.call(ourTp,ourTp,ourTab);
		if(!contextItems || contextItems.length == 0) { return; }
		
		optionsMenu.insert(0,'-');
		Ext.each(contextItems.reverse(),function(itm){ optionsMenu.insert(0,itm); },this);
		
		// Optional hook into an items 'hideShow' function. Used by close to check if there
		// are other tabs to close and hide itself
		optionsMenu.items.each(function(item){
			if(Ext.isFunction(item.hideShow)) {
				optionsMenu.on('beforeshow',item.hideShow,item);
			}
		},this);
	},
  
  editHeaderIcoDomCfg: {
		tag: 'div',
		cls: 'ra-icon-gray-pencil-tiny',
		style: 'position:absolute;right:1px;top:1px;width:7px;height:7px;'
	},
  
  updateEditHeaders: function() {
    this.hdEdIcos = this.hdEdIcos || {};
    var view = this.getView(),hds, i, len;
    if (view.mainHd) {

      hds = view.mainHd.select('td');
      for (i = 0, len = view.cm.config.length; i < len; i++) {
        var itm = hds.item(i);
        
        if(this.hdEdIcos[i]) { this.hdEdIcos[i].remove(); delete this.hdEdIcos[i]; }
        var column = view.cm.config[i];
        var editable = this.getStore().isEditableColumn(column.name);
        if (editable) {
          //console.dir([itm.child('div')]);
          this.hdEdIcos[i] = itm.child('div').insertFirst(this.editHeaderIcoDomCfg);
        }
      }
    }
  
  }
};

Ext.ux.RapidApp.AppTab.AppGrid2 = Ext.extend(Ext.grid.GridPanel,Ext.ux.RapidApp.AppTab.AppGrid2Def);
Ext.reg('appgrid2', Ext.ux.RapidApp.AppTab.AppGrid2);

Ext.ux.RapidApp.AppTab.AppGrid2Ed = Ext.extend(Ext.grid.EditorGridPanel,Ext.ux.RapidApp.AppTab.AppGrid2Def);
Ext.reg('appgrid2ed', Ext.ux.RapidApp.AppTab.AppGrid2Ed);

Ext.ns('Ext.ux.RapidApp.AppTab.AppGrid2');

Ext.ux.RapidApp.AppTab.AppGrid2.ExcelExportMenu = Ext.extend(Ext.menu.Menu,{

	url: null,

	initComponent: function() {

		this.items = [
			{
				//text: 'This Page, Active Columns',
				text: 'Current Page',
				iconCls: 'ra-icon-table-selection-row',
				handler: function(item) {
					var cmp = item.ownerCt;
					Ext.ux.RapidApp.AppTab.AppGrid2.excelExportHandler.call(this,cmp,cmp.url,false,false);
				},
				scope: this
			},
			
			/*
			{
				text: 'This Page, All Columns',
				handler: function(item) {
					var cmp = item.ownerCt;
					Ext.ux.RapidApp.AppTab.AppGrid2.excelExportHandler(cmp,cmp.url,false,true);
				}
			}*/
			
			{
				//text: 'All Pages, Active Columns',
				text: 'All Pages',
				iconCls: 'ra-icon-table-selection-all',
				handler: function(item) {
					var cmp = item.ownerCt;
					Ext.ux.RapidApp.AppTab.AppGrid2.excelExportHandler(cmp,cmp.url,true,false);
				}
			}
			
			/*,
			{
				text: 'All Pages, All Columns',
				handler: function(item) {
					var cmp = item.ownerCt;
					Ext.ux.RapidApp.AppTab.AppGrid2.excelExportHandler(cmp,cmp.url,true,true);
				}
			}
			*/
		];
		
		Ext.ux.RapidApp.AppTab.AppGrid2.ExcelExportMenu.superclass.initComponent.call(this);
	}
});



Ext.ux.RapidApp.AppTab.AppGrid2.excelExportHandler = function(cmp,url,all_pages,all_columns) {
	
	var btn = Ext.getCmp(cmp.buttonId);
	var grid = btn.findParentByType("appgrid2") || btn.findParentByType("appgrid2ed");
	
	var export_filename = grid.title || grid.ownerCt.title || 'export';
	
	Ext.Msg.show({
		title: "Excel Export",
		msg: "Export current view to Excel File? <br><br>(This might take up to a few minutes depending on the number of rows)",
		buttons: Ext.Msg.YESNO, fn: function(sel){
			if(sel != "yes") return; 
			
			var store = grid.getStore();
			//var params = {};
			
			// -- Get the params that the store last used to fetch from the server
			// There is no built-in method to get this info, so this logic is basically
			// copied from the load method of Ext.data.Store:
			var options = Ext.apply({}, store.lastOptions);
			if(store.sortInfo && store.remoteSort){
				var pn = store.paramNames;
				options.params = Ext.apply({}, options.params);
				options.params[pn.sort] = store.sortInfo.field;
				options.params[pn.dir] = store.sortInfo.direction;
			}
			// --
			Ext.apply(options.params,store.baseParams);
			
			/*
			if(store.filterdata) {
				var encoded = Ext.encode(store.filterdata);
				Ext.apply(options.params, {
					'multifilter': encoded 
				});
			}
			*/
			
			if(all_pages) { 
				if (options.params.limit) { delete options.params.limit; } 
				if (options.params.start) { delete options.params.start; } 
			}
			
			if(all_columns && options.params.columns) { delete options.params.columns; }
			
			//return Ext.ux.postwith(url,options.params);
			
			options.params.export_filename = export_filename;
			
			var timeout = 900000; // 15-minutes
      
      // ---------------
      // 2013-09-21 by HV:
      // CUSTOM FIREFOX HANDLING BYPASSED/DISABLED (See Github Issue #7)
      //  This interactive mode is better, but only ever worked in
      //  FireFox, but recently, it has stopped working there too, so
      //  this code is just being bypassed for now, but I'm leaving the
      //  code here for reference later on
			if(false && Ext.isGecko) { // FireFox (<-- now always false)
				// Interactive window download:
				return Ext.ux.RapidApp.winDownload(
					url,options.params,"Exporting data to Excel...",timeout
				);
			}
      // ---------------
			else {
				// Background download, since non-FF browsers can't detect download complete and
				// close the window:
				
				// UPDATE: had to revert to using iFramePostwith because iframeBgDownload
				// fails when there are too many params which results in an encoded URL which
				// is too long, which happens with grid views with lots of columns or filter
				// criteria.
				//return Ext.ux.iframeBgDownload(url,options.params,timeout);
				return Ext.ux.iFramePostwith(url,options.params);
				
			}
			
		},
		scope: cmp
	});
}


Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.confirmDialogCall = function(title,msg,fn) {
	Ext.Msg.show({
			title: title,
			msg: msg,
			buttons: Ext.Msg.YESNO,
			icon: Ext.MessageBox.QUESTION,
			fn: function(buttonId) { 
				if (buttonId=="yes") {
					return fn();
				}
			}
	});
}


/*
 If this is used as the column renderer for an AppGrid2 column,
 the same icon used in the tab when a row is opened will be displayed
 to the left of the value of the cell for the given column (assumes 16x16 icon)
 this is pulled out of the 'loadContentCnf' JSON encoded data
*/
Ext.ux.RapidApp.AppTab.iconClsColumnRenderer = function(value, metaData, record, rowIndex, colIndex, store) {
  if (record.data.loadContentCnf) {
    var loadCfg = Ext.decode(record.data.loadContentCnf);
    if(loadCfg.iconCls) {
      metaData.css = 'grid-cell-with-icon ' + loadCfg.iconCls;
    }
  }
  return value;
}

Ext.ns('Ext.ux.RapidApp');

Ext.ux.RapidApp.AppTree = Ext.extend(Ext.tree.TreePanel,{
	
	add_node_text: 'Add',
	add_node_iconCls: 'ra-icon-add',
	add_node_url: null,
	
	delete_node_text: 'Delete',
	delete_node_iconCls: 'ra-icon-delete',
	delete_node_url: null,
	
	rename_node_text: 'Rename',
	rename_node_iconCls: 'ra-icon-textfield-rename',
	rename_node_url: null,
	
	reload_node_text: 'Reload',
	reload_node_iconCls: 'ra-icon-refresh',
	
	copy_node_text: 'Copy',
	copy_node_iconCls: 'ra-icon-element-copy',
	copy_node_url: null,
	
	node_action_reload: true,
	node_action_expandall: true,
	node_action_collapseall: true,
	
	use_contextmenu: false,
	no_dragdrop_menu: false,
	setup_tbar: false,
	no_recursive_delete: false,
	no_recursive_copy: false,
	
	// Controls if nodes can drag/drop between nodes as well as into (append) nodes
	ddAppendOnly: true,
	
	// Set this to true to display extra options to dump the node and tree to
	// the firebug console in the node right-click context menu
	debug_menu_options: false,
	
	initComponent: function() {
		
		this.initDragAndDrop();
		
		if(!this.node_actions) {
			this.node_actions = [];
			
			if(this.node_action_reload) {
				this.node_actions.push({
					text: this.reload_node_text,
					iconCls: this.reload_node_iconCls,
					handler: this.nodeReload,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.node_action_expandall) {
				this.node_actions.push({
					text: 'Expand All',
					iconCls: 'ra-icon-tree-expand',
					handler: this.nodeExpandAll,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.node_action_collapseall) {
				this.node_actions.push({
					text: 'Collapse All',
					iconCls: 'ra-icon-tree-collapse',
					handler: this.nodeCollapseAll,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			
			if(this.node_actions.length > 0) {
				this.node_actions.push('-');
			}
			
			if(this.rename_node_url) {
				this.node_actions.push({
					text: this.rename_node_text,
					iconCls: this.rename_node_iconCls,
					handler: this.nodeRename,
					rootValid: false,
					leafValid: true,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.delete_node_url) {
				this.node_actions.push({
					text: this.delete_node_text,
					iconCls: this.delete_node_iconCls,
					handler: this.nodeDelete,
					rootValid: false,
					leafValid: true,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.add_node_url) {
				this.node_actions.push({
					text: this.add_node_text,
					iconCls: this.add_node_iconCls,
					handler: this.nodeAdd,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: false
				});
			}
			
			if(this.copy_node_url) {
				this.node_actions.push({
					text: this.copy_node_text,
					iconCls: this.copy_node_iconCls,
					handler: this.nodeCopyInPlace,
					rootValid: false,
					leafValid: true,
					noTbar: false,
					tbarIconOnly: false
				});
			}
			
			if(this.expand_node_url) {
				this.on('expandnode',function(node){
					this.persistNodeExpandState(node,1);
				},this);
				this.on('collapsenode',function(node){
					this.persistNodeExpandState(node,0);
				},this);
			}
			
				
			if(Ext.isArray(this.extra_node_actions)) {
				Ext.each(this.extra_node_actions,function(action) {
					this.node_actions.push(action);
				},this);
			}
			
			// Remove the divider if it is the last item:
			if(this.node_actions.length > 0 && this.node_actions[this.node_actions.length - 1] == '-') {
				this.node_actions.pop();
			}
		}
		
		if(this.setup_tbar) {
		
			var init_tbar_items = [];
			if(Ext.isArray(this.tbar)) { init_tbar_items = this.tbar; }
			if(!Ext.isObject(this.tbar)) {
				this.tbar = {
					xtype: 'toolbar',
					enableOverflow: true,
					items: init_tbar_items
				};
			}
			
			var tbar_items = this.getTbarActionsButtons();
			if(tbar_items.length > 0) {
				this.tbar.items.push('->');
				Ext.each(tbar_items,function(item) {
					this.tbar.items.push(item);
				},this);
			}
		}
		
		if(this.use_contextmenu) { 
			this.on('contextmenu',this.onContextmenu,this);
		}
		
		this.on('afterrender',function() {
			// Init button states with the root node first:
			this.notifyActionButtons(this.root);
			
			this.getSelectionModel().on('selectionchange',function(selMod,node) {
				this.notifyActionButtons(node);
			},this);
		},this);
		
		
		
		Ext.ux.RapidApp.AppTree.superclass.initComponent.call(this);
	},
	
	persistNodeExpandState: function(node,state) {
		if(node == this.root) { return false; } // <-- ignore the root node
		this.queuePersistExpandUpdates(node.id,state);
	},
	
	queuePersistExpandUpdates: function(id,state) {
		this.initPersistExpandQueue();
		this.persistExpandQueue.nodes.push(id);
		this.persistExpandQueue.states.push(state);
		
		if(!this.processPersistExpandPending) {
			this.processPersistExpandPending = true;
			this.processPersistExpandQueue.defer(1000,this);
		}
	},
	
	initPersistExpandQueue: function(delete_current) {
		if(delete_current && this.persistExpandQueue) { 
			delete this.persistExpandQueue;
		}
		if(! this.persistExpandQueue) {
			this.persistExpandQueue = { nodes: [], states: [] };
		}
	},
	
	processPersistExpandQueue: function() {
		this.processPersistExpandPending = false;
		
		// do nothing if the queue is empty:
		if(this.persistExpandQueue.nodes.length == 0) { return true; }
		
		var queue = this.persistExpandQueue;
		this.initPersistExpandQueue(true);
		
		Ext.Ajax.request({
			url: this.expand_node_url,
			params: { node: queue.nodes, expanded: queue.states },
			scope: this,
			success: Ext.emptyFn //<-- assume it worked, don't do anything if it didn't
		});
	},
	
	initDragAndDrop: function() {
		if(this.copy_node_url || this.move_node_url) {
			this.enableDD = true;
			//this.ddAppendOnly = true; //<-- this disables setting "order"
			this.on('nodedragover',this.onNodeDragOver,this);
			this.on('beforenodedrop',this.beforeNodeDropHandler,this);
		}
	},
	
	onNodeDragOver: function(dragOverEvent) {
		var t = dragOverEvent.target;
		var leafOnly = false;
		
		// Nodes with allowLeafDropOnly will only allow leaf nodes dropped on them:
		if(t.attributes.allowLeafDropOnly) { leafOnly = true; }
		
		// parents can also restrict their children with allowChildrenLeafDropOnly:
		if(t.parentNode && t.parentNode.attributes.allowChildrenLeafDropOnly) { leafOnly = true; }
		
		if(leafOnly && !dragOverEvent.data.node.isLeaf()) {
			dragOverEvent.cancel = true;
		}
	},
	
	beforeNodeDropHandler: function(dropEvent) {
		// nothing but 'append' should get this far if ddAppendOnly is true
		if(this.ddAppendOnly && dropEvent.point !== 'append') { return; }
		
		var node = dropEvent.data.node;
		var target = dropEvent.target;
		var e = dropEvent.rawEvent;
		var point = dropEvent.point;
		var point_node;
		
		// point of 'before' or 'after' for order/positioning:
		if(point !== 'append') {
			point_node = target;
			target = target.parentNode;
		}
		
		if(this.nodeDropMenu(node,target,e,point,point_node)) {
			// If we're here it means that the menu has been displayed.
			// We are setting these attributes to prevent the "repair" ui
			// since we have to run an async round-trip to the server
			dropEvent.cancel = true;
			dropEvent.dropStatus = true;
		}
	},
	
	nodeDropMenu: function(node,target,e,point,point_node) {

		var menuItems = [];
		
		/* New: Disable drop/copy option if 'no_dragdrop_menu' is true.
		   the original logic/intent was that this setting would allow
		   either *copy* or *move* to happen automatically, but after
		   thinking about it more, automatic drag/drop copy doesn't make
		   a lot of sense, and now a "Copy In-place" option is being added
		   as a right-click "action" and will use 'copy_node_url' too, so
		   I am just disabling this auto copy or auto move feature for now,
		   leaving only 'auto move' when no_dragdrop_menu is turned on
		*/
		if(this.copy_node_url && !this.no_dragdrop_menu) {
			menuItems.push({
				text: 'Copy here',
				iconCls: 'ra-icon-element-copy',
				handler: function(no_reloads) { 
					this.nodeCopyMove(node,target,this.copy_node_url,false,point,point_node,no_reloads); 
				},
				scope: this
			});
		}
		
		if(this.move_node_url) {
			menuItems.push({
				text: 'Move here',
				iconCls: 'ra-icon-element-into',
				handler: function(no_reloads) { 
					this.nodeCopyMove(node,target,this.move_node_url,true,point,point_node,no_reloads); 
				},
				scope: this
			});
		}
		
		if(!menuItems.length) { return false; }
		
		// -- If no drop menu is set, and there is exactly 1 option (copy or move, but not both), 
		// run that one option automatically:
		// Update:
		//  see above 'copy' comment. menuItems.length should now always be 1 if no_dragdrop_menu
		//  is on, but I am leaving the redundant check in place in case this auto copy or auto
		//  move feature wants to be turned back on...
		if(this.no_dragdrop_menu && menuItems.length == 1) {
			var item = menuItems[0];
			var no_reloads = true;
			item.handler.defer(10,item.scope,[no_reloads]);
			//return true;
			
			// return false to *prevent* cancelling the GUI drag/drop:
			return false;
		}
		// --
		
		
		menuItems.push('-',{
			text: 'Cancel',
			iconCls: 'x-tool x-tool-close',
			handler: Ext.emptyFn
		});
		
		var menu = new Ext.menu.Menu({ items: menuItems });
		var pos = e.getXY();
		pos[0] = pos[0] + 10;
		pos[1] = pos[1] + 5;
		menu.showAt(pos);
		return true;
	},
	
	nodeCopyMove: function(node,target,url,remSrc,point,point_node,no_reloads) {
		
		var params = { 
			node: node.id,
			target: target.id,
			point: point
		};
		
		if(point_node) { params.point_node = point_node.id; }
		
		Ext.Ajax.request({
			url: url,
			params: params,
			scope: this,
			success: function() {
				
				// no_reloads will be on when there is only one copy/move action
				// setup and thus no need for a menu, and thus no need to cancel
				// the GUI move, and thus no need to do node reloading because
				// the GUI move operation is properly tracking what happened by itself:
				if(!no_reloads) {
					
					// no_reloads also overrides/disables remSrc setting... so far this
					// logic was tested and needed with "move" as the only DD operation
					// and no menu display... again, since we don't cancel the GUI move,
					// ExtJS is automatically handling this... TODO: what happens if copy
					// were the only operation with no menu? Would that even make sense? 
					// probably not...
					if(remSrc) {
						node.parentNode.removeChild(node,true);
					}

					this.nodeReload(target);
				}
			},
			failure: function() {
				// If the operation failed on the server side, reload the whole tree to
				// be safe and avoid any possible interface/database inconsistency
				this.nodeReload(this.root);
			}
		});
	},
	
	actionValidForNode: function(action,node) {
		if(!node) { return false; }
		
		/* Broad validation by node type and action rules: */
		if(!action.rootValid && (node == this.root || node.attributes.rootValidActions)) { 
			return false; 
		}
		
		if(!action.leafValid && node.isLeaf()) { 
			return false; 
		}
		
		
		/* Per-action name validations: */
		if(action.text == this.add_node_text) {
			// The add action can be turned off for any given node by setting "allowDelete" to false:
			if(typeof node.attributes.allowAdd !== "undefined" && !node.attributes.allowAdd) {
				return false;
			}
		}
		
		else if(action.text == this.rename_node_text) {
			// The rename action can be turned off for any given node by setting "allowRename" to false:
			if(typeof node.attributes.allowRename !== "undefined" && !node.attributes.allowRename) {
				return false;
			}
		}
		
		else if(action.text == this.reload_node_text) {
			// Nodes with static array of children can't be reloaded from the server:
			// Update: this is now handled in the nodeReload function
			//if(typeof node.attributes.children !== "undefined") {
			//	return false;
			//}
			
			// The reload action can be turned off for any given node by setting "allowReload" to false:
			if(typeof node.attributes.allowReload !== "undefined" && !node.attributes.allowReload) {
				return false;
			}
		}
		
		else if(action.text == this.delete_node_text) {
			if(this.no_recursive_delete && node.isLoaded && node.isLoaded() && node.hasChildNodes()) { 
				return false; 
			}
			// The delete action can be turned off for any given node by setting "allowDelete" to false:
			if(typeof node.attributes.allowDelete !== "undefined" && !node.attributes.allowDelete) {
				return false;
			}
		}
		
		
		else if(action.text == this.copy_node_text) {
			if(this.no_recursive_copy && node.isLoaded && node.isLoaded() && node.hasChildNodes()) { 
				return false; 
			}
			// The copy action can be turned off for any given node by setting "allowCopy" to false:
			if(typeof node.attributes.allowCopy !== "undefined" && !node.attributes.allowCopy) {
				return false;
			}
		}
		
		// If we made it to the end without being invalidated, then the action is valid for this node:
		return true;
	},
	
	notifyActionButtons: function(node) {
		Ext.each(this.tbarActionsButtons,function(btn) {
			if(btn.notifyCurrentNode) {
				btn.notifyCurrentNode.call(btn,node);
			}
		},this);
	},
	
	getTbarActionsButtons: function() {
		var items = [];
		Ext.each(this.node_actions,function(action) {
			if(Ext.isString(action)) {
				items.push(action);
				return;
			}
			var cnf = {
				tree: this,
				nodeAction: action,
				xtype: 'button',
				text: action.text,
				iconCls: action.iconCls,
				handler: function() {
					var node = this.getSelectionModel().getSelectedNode();
					action.handler.call(this,node);
				},
				scope: this
			};
			if (action.tbarIconOnly) {
				cnf.tooltip = cnf.text;
				cnf.overflowText = cnf.text;
				delete cnf.text;
			}
			
			cnf.notifyCurrentNode = function(node) {
				var valid = this.tree.actionValidForNode(this.nodeAction,node);
				this.setDisabled(!valid);
			}
			
			var button = new Ext.Button(cnf);
			items.push(button);
		},this);
		this.tbarActionsButtons = items;
		return this.tbarActionsButtons;
	},
	
	onContextmenu: function(node,e) {

		var menuItems = [];
		Ext.each(this.node_actions,function(action) {
			if(Ext.isString(action)) {
				// Prevent adding a divider as the first item:
				if(action == '-' && menuItems.length == 0) { return; }
				menuItems.push(action);
				return;
			}
			if(!this.actionValidForNode(action,node)) { return; }
			menuItems.push({
				text: action.text,
				iconCls: action.iconCls,
				handler: function() { action.handler.call(this,node); },
				scope: this
			});
			
		},this);
		
		
		
		//-- for debugging:
		if(this.debug_menu_options) {
			menuItems.push(
				'-',
				{
					text: 'console.dir(node)',
					handler: function() { console.dir(node); }
				},
				{
					text: 'console.dir(tree)',
					handler: function() { console.dir(node.getOwnerTree()); }
				}
			);
		}
		//--
		
		// remove a divider if it ends up as the last item:
		if(menuItems.length && menuItems[menuItems.length-1] == '-') {
			menuItems.pop();
		}
		
		
		if(menuItems.length == 0){ return false; }
		
		var menu = new Ext.menu.Menu({ items: menuItems });
		node.select();
		var pos = e.getXY();
		pos[0] = pos[0] + 10;
		pos[1] = pos[1] + 5;
		menu.showAt(pos);
	},
	
	nodeReload: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		return this.nodeReloadRecursive(node);
	},
	
	// Recursively calls itself on parent nodes until it reaches a 
	// node that can be reloaded:
	nodeReloadRecursive: function(node) {
		node = node || this.root; //<-- default to the root node
		if(node !== this.root) {
			// Leaf nodes can't be reloaded from the server, but neither can
			// non-leaf nodes if they have a static defined list of children:
			if(node.isLeaf() || node.attributes.children) {
				return this.nodeReloadRecursive(node.parentNode);
			}
		}
		this.getLoader().load(node,function(tp){
			node.expand();
		});
	},
	
	nodeExpandAll: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		if(node.isLeaf() && node.parentNode) { node = node.parentNode; }
		node.expand(true);
	},
	
	nodeCollapseAll: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		if(node.isLeaf() && node.parentNode) { node = node.parentNode; }
		node.collapse(true);
	},
	
	nodeRename: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		if(node == this.root) { return; }
		return this.nodeApplyDialog(node,{
			title: this.rename_node_text,
			url: this.rename_node_url,
			value: node.attributes.text
		});
	},
	
	nodeAdd: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		
		return this.nodeApplyDialog(node,{
			title: this.add_node_text,
			url: this.add_node_url
		});
	},
	
	nodeDelete: function(node) {
		if (! node) { 
			Ext.Msg.alert('Nothing selected to Delete','You must select an item to delete.');
			return;
		}
		// Ignore attempts to delete the root node:
		if(node == this.root) { return; }
		var tree = this;
		var params = { node: node.id };

		var ajaxFunc = function() {
			Ext.Ajax.request({
				url: tree.delete_node_url,
				params: params,
				success: function() {
					node.parentNode.removeChild(node,true);
					//var pnode = node.parentNode;
					//tree.getLoader().load(pnode,function(tp){
					//	pnode.expand();
					//});
				}
			});
		};

		var Func = ajaxFunc;

		if (node.hasChildNodes()) {
			
			if(this.no_recursive_delete) {
				Ext.Msg.alert(
					'Cannot Delete',
					'"' + node.attributes.text + '" cannot be deleted because it contains child items.'
				);
				return;
			}
			
			params['recursive'] = true;
			Func = function() {
				Ext.ux.RapidApp.confirmDialogCall(
					'Confirm Recursive Delete',
					'"' + node.attributes.text + '" contains child items, they will all be deleted.<br><br>' +
					 'Are you sure you want to continue ?',
					ajaxFunc
				);
			}
		}

		Ext.ux.RapidApp.confirmDialogCall(
			'Confirm Delete',
			'Really delete "' + node.attributes.text + '" ?',
			Func
		);
	},
	
	// This works like an action (right-click) instead of a drag-drop
	// like nodeCopyMove. So it is really more like nodeAdd
	nodeCopyInPlace: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }

		return this.nodeApplyDialog(node,{
			title: this.copy_node_text,
			url: this.copy_node_url,
			params : {
				node: node.id,
				target: node.parentNode.id,
				point: 'below',
				point_node: node.id
			},
			value: node.attributes.text + ' (Copy)'
		});
	},
	
	// General purpose functon for several operations, like add, rename
	nodeApplyDialog: function(node,opt) {
		var tree = this;
		var cnf = Ext.apply({
			url: null, // <-- url is required
			title: 'Apply Node',
			name: 'name',
			fieldLabel: 'Name',
			labelWidth: 40,
			height: 130,
			width: 350,
			params: { node: node.id },
			value: null
		},opt);
		
		if(!cnf.url) { throw "url is a required parameter"; }
		
		var Field = Ext.create({
			xtype: 'textfield',
			name: cnf.name,
			fieldLabel: cnf.fieldLabel,
			value: cnf.value,
			anchor: '100%'
		},'field');
		
		Field.on('afterrender',function(field){ field.show.defer(300,field); });
		
		//Focus the field and put the cursor at the end
		Field.on('show',function(field){
			field.focus();
			field.setCursorPosition(1000000);
		},this);

		var fieldset = {
			xtype: 'fieldset',
			style: 'border: none',
			hideBorders: true,
			labelWidth: cnf.labelWidth,
			border: false,
			//items: items
			items: Field
		};

		var winform_cfg = {
			title: cnf.title,
			height: cnf.height,
			width: cnf.width,
			url: cnf.url,
			useSubmit: true,
			params: cnf.params,
			fieldset: fieldset,
			
			success: function(response,options) {
				var res = options.result;
				
				// if 'new_text' is supplied in the response then update the text of current node
				if (res.new_text) {
					node.setText(res.new_text);
				}
				
				// if 'child' is supplied in the response then we add it as a child to the current node
				if (res.child) {
					var newChild = tree.getLoader().createNode(res.child);
					
					if(res.child_after) { // <-- for 'copy in place'
						node.parentNode.insertBefore(newChild,node.nextSibling);
					}
					else {
						node.expand();
						node.appendChild(newChild);
					}
					//newChild.ensureVisible();
				}
				
				// If neither 'child' nor 'new_text' is in the reponse we reload the node
				if(!res.new_text && !res.child) {
					tree.nodeReload(node);
					
				}
				
				
			}
		};
		Ext.ux.RapidApp.WinFormPost(winform_cfg);
	},
	
	activeNonLeafNode: function() {
		var node = this.getSelectionModel().getSelectedNode();
		if(node) {
			// If this is a leaf node, it can't have childred, so use the parent node:
			if(node.isLeaf() && node.parentNode) { 
				node = node.parentNode;
			}
		}
		else {
			node = this.root;
		}
		return node;
	}
	
});
Ext.reg('apptree',Ext.ux.RapidApp.AppTree);



Ext.ux.RapidApp.AppTree_rename_node = function(node) {
	var tree = node.getOwnerTree();

	return tree.nodeApplyDialog(node,{
		title: "Rename",
		url: tree.rename_node_url,
		value: node.attributes.text
	});
	
	
	var items = [
		{
			xtype: 'textfield',
			name: 'name',
			fieldLabel: 'Name',
			value: node.attributes.text,
			anchor: '100%',
			listeners: {
				'afterrender': function() { 
					// try to focus the field:
					this.focus('',10); 
					this.focus('',200);
					this.focus('',500);
				}
			}
		}
	];

	var fieldset = {
		xtype: 'fieldset',
		style: 'border: none',
		hideBorders: true,
		labelWidth: 40,
		border: false,
		items: items
	};

	var winform_cfg = {
		title: "Rename",
		height: 130,
		width: 350,
		url: tree.rename_node_url,
		useSubmit: true,
		params: {
			node: node.id
		},
		fieldset: fieldset,
		
		success: function(response,options) {
			var res = options.result;
			if(res.new_name) {
				node.setText(res.new_name);
			}
		}
	};
	
	Ext.ux.RapidApp.WinFormPost(winform_cfg);
}


Ext.ux.RapidApp.AppTree_contextmenu_handler = function(node,e) {

		var menu = new Ext.menu.Menu({
			items: [{
				iconCls: 'ra-icon-textfield-rename',
				text: 'Rename',
				handler: function(item) {
					Ext.ux.RapidApp.AppTree_rename_node(node);
				}
			}]
		});
		node.select();
		menu.showAt(e.getXY());
		//menu.show(node.ui.getEl());
}

Ext.ux.RapidApp.AppTree_select_handler = function(tree) {

	var node = tree.getSelectionModel().getSelectedNode();

	return {
		value: node.id,
		display: node.attributes.text
	};

}


Ext.ux.RapidApp.AppTree_setValue_translator = function(val,tf,url) {
	if(val.indexOf('/') > 0) { tf.translated = false; }
	if(!tf.translated) {
		Ext.Ajax.request({
			url: url,
			params: { node: val },
			success: function(response) {
				var res = Ext.decode(response.responseText);
				tf.translated = true; // <-- prevent recursion
				tf.dataValue = res.id;
				tf.setValue(res.text);
			}
		});
	}
	else {
		return val;
	}
}


Ext.ns('Ext.ux.RapidApp.AppTree');
Ext.ux.RapidApp.AppTree.jump_to_node_id = function(tree,id) {

	var parents_arr = function(path,arr) {
		if (!arr) arr = [];
		if (path.indexOf('/') < 0) {
			return arr;
		}

		var path_arr = path.split('/');

		var item = path_arr.pop();
		var path_str = path_arr.join('/');
		arr.push(path_str);
		return parents_arr(path_str,arr);
	}

	var select_child = function(id,parents,lastpass) {

		var par = parents.pop();
		if(!par) return;

		var node = tree.getNodeById(par);
		if(!node) return;

		node.loaded = false;
		node.expand(false,false,function(){
			if(parents.length > 0) {
				select_child(id,parents);
			}
			else {
				node.select();
			}
		});
	}

	var parents = parents_arr(id);
	parents.unshift(id);

	return select_child(id,parents);
};


Ext.ux.RapidApp.AppTree.get_selected_node = function(tree) {

	var node = tree.getSelectionModel().getSelectedNode();
	if(node) {
		// If this is a leaf node, it can't have childred, so use the parent node:
		if(node.isLeaf() && node.parentNode) { 
			var parent = node.parentNode;
			node = parent;
		}
		id = node.id;
	}
	else {
		node = tree.root;
	}

	return node;
}

Ext.ux.RapidApp.AppTree.add = function(tree,cfg) {

	var url;
	if (Ext.isObject(cfg)) {
	
	}
	else {
		url = cfg;
	}
	
	var items = [
		{
			xtype: 'textfield',
			name: 'name',
			fieldLabel: 'Name',
			listeners: {
				'afterrender': function() { 
					// try to focus the field:
					this.focus('',10); 
					this.focus('',200);
					this.focus('',500);
				}
			}
		}
	];

	var fieldset = {
		xtype: 'fieldset',
		style: 'border: none',
		hideBorders: true,
		labelWidth: 60,
		border: false,
		items: items
	};

	var node = Ext.ux.RapidApp.AppTree.get_selected_node(tree);
	var id = node.id;

/*
	var node = tree.getSelectionModel().getSelectedNode();
	var id = "root";
	if(node) {
		// If this is a leaf node, it can't have childred, so use the parent node:
		if(node.isLeaf() && node.parentNode) { 
			var parent = node.parentNode;
			node = parent;
		}
		id = node.id;
	}
*/
	
	var winform_cfg = {
		title: "Add",
		height: 130,
		width: 250,
		url: url,
		useSubmit: true,
		params: {
			node: id
		},
		fieldset: fieldset,
		success: function(response) {
			tree.getLoader().load(node,function(tp){
				node.expand();
			});
		}
	};
	
	Ext.apply(winform_cfg,cfg);
	Ext.ux.RapidApp.WinFormPost(winform_cfg);
}


Ext.ux.RapidApp.AppTree.del = function(tree,url) {

	var node = tree.getSelectionModel().getSelectedNode();
	var id = "root";
	if(node) id = node.id;

	var params = {
		node: id
	};

	var ajaxFunc = function() {
		Ext.Ajax.request({
			url: url,
			params: params,
			success: function() {
				var pnode = node.parentNode;
				tree.getLoader().load(pnode,function(tp){
					pnode.expand();
				});
			}
		});
	};

	var Func = ajaxFunc;

	if (node.hasChildNodes()) {
		params['recursive'] = true;
		Func = function() {
			Ext.ux.RapidApp.confirmDialogCall(
				'Confirm Recursive Delete',
				'"' + node.attributes.text + '" contains child items, they will all be deleted.<br><br>' +
				 'Are you sure you want to continue ?',
				ajaxFunc
			);
		}
	}

	Ext.ux.RapidApp.confirmDialogCall(
		'Confirm Delete',
		'Really delete "' + node.attributes.text + '" ?',
		Func
	);
}







Ext.ux.RapidApp.AppTree.ensure_recursive_load = function(tree,callback,scope) {
	
	var func = function() {
		if(callback) {
			if(!scope) { scope = tree; }
			callback.call(scope);
		}
	};
	
	if(tree.recursive_load_complete) { return func(); }
	
	var pnode = tree.root;
	var expand_func;
	expand_func = function(node) {
		tree.recursive_load_complete = true;
		this.un('expand',expand_func);
		func();
	}
	pnode.on('expand',expand_func,pnode);
	pnode.collapse();
	pnode.loaded = false;
	
	var loader = tree.getLoader();
	
	var rfunc;
	rfunc = function(treeLoader,node) {
		this.baseParams.recursive = true;
		this.un("beforeload",rfunc);
	}
	loader.on("beforeload",rfunc,loader);
	
	pnode.expand(true,false);
}


Ext.ns('Ext.ux.RapidApp.AppTree');

Ext.ux.RapidApp.AppTree.FilterPlugin = Ext.extend(Ext.util.Observable,{
	
	fieldIndex: 0,
	
	init: function(tree) {
		this.tree = tree;
		var Filter = this;
		
		if(tree.filterConfig) { Ext.apply(this,tree.filterConfig); }

		var fieldConfig = {
			xtype:'trigger',
			emptyText: 'Type to Find',
			triggerClass:'x-form-clear-trigger',
			onTriggerClick:function() {
				this.setValue('');
				tree.filter.clear();
			},
			enableKeyEvents:true,
			listeners:{
				keyup:{
					buffer: 150, 
					fn: function(field, e) {
						if(Ext.EventObject.ESC == e.getKey()) {
							field.onTriggerClick();
						}
						//else {
						else if (Ext.EventObject.ENTER == e.getKey()){
							//Filter.treeLoadAll();
							var callback = function() {
								var val = field.getRawValue();
								Ext.ux.RapidApp.AppTree.set_next_treeload_params(tree,{search:val});
								var re = new RegExp('.*' + val + '.*', 'i');
								tree.filter.clear();
								tree.filter.filter(re, 'text');
							}
							
							Ext.ux.RapidApp.AppTree.ensure_recursive_load(tree,callback);
						}
					}
				}
			}
		};
			
		if(this.fieldConfig) {
			Ext.apply(fieldConfig,this.fieldConfig);
		}
		
		tree.filter = new Ext.ux.tree.TreeFilterX(tree);
		tree.filter.searchField = Ext.ComponentMgr.create(fieldConfig);
		var Tbar = tree.getTopToolbar();
		Tbar.insert(this.fieldIndex,tree.filter.searchField);
	}
});
Ext.preg('apptree-filter',Ext.ux.RapidApp.AppTree.FilterPlugin);



Ext.ux.RapidApp.AppTree.reload = function(tree,recursive) {
	if(Ext.isFunction(tree.onReload)) { tree.onReload.call(tree); }
	tree.root.collapse();
	tree.root.loaded = false;
	tree.root.expand();
}

Ext.ux.RapidApp.AppTree.ServerFilterPlugin = Ext.extend(Ext.util.Observable,{
	
	fieldIndex: 0,
	
	init: function(tree) {
		this.tree = tree;
		
		tree.onReload = function() {
			delete tree.next_load_params;
			tree.searchField.setValue('');
		};

		var loader = tree.getLoader();
		loader.on("beforeload",function(){
			this.baseParams = {};
			if(tree.next_load_params) {
				this.baseParams = tree.next_load_params;
				delete tree.next_load_params;
			}
		});
		
		if(tree.filterConfig) { Ext.apply(this,tree.filterConfig); }

		var fieldConfig = {
			emptyText: 'Type to Find',
			trigger1Class:'x-form-clear-trigger',
			trigger2Class: 'x-form-search-trigger',
			onTrigger1Click: function() {
				Ext.ux.RapidApp.AppTree.reload(tree);
			},
			onTrigger2Click:function() {
				this.runSearch.call(this);
			},
			runSearch: function() {
				var val = this.getRawValue();
				if(val == '') { return this.onTrigger1Click(); }
				tree.next_load_params = {
					search: val,
					recursive: true
				};
				tree.root.collapse();
				tree.root.loaded = false;
				tree.root.expand();
			},
			enableKeyEvents:true,
			listeners:{
				keyup:{
					buffer: 150, 
					fn: function(field, e) {
						if(Ext.EventObject.ESC == e.getKey()) {
							field.onTrigger1Click();
						}
						else if (Ext.EventObject.ENTER == e.getKey()){
							return field.runSearch();
						}
					}
				}
			}
		};
			
		if(this.fieldConfig) {
			Ext.apply(fieldConfig,this.fieldConfig);
		}
		
		tree.searchField = new Ext.form.TwinTriggerField(fieldConfig);
		var Tbar = tree.getTopToolbar();
		Tbar.insert(this.fieldIndex,tree.searchField);
	}
});
Ext.preg('apptree-serverfilter',Ext.ux.RapidApp.AppTree.ServerFilterPlugin);




/**
 * @class   Ext.ux.tree.TreeFilterX
 * @extends Ext.tree.TreeFilter
 *
 * <p>
 * Shows also parents of matching nodes as opposed to default TreeFilter. In other words
 * this filter works "deep way".
 * </p>
 *
 * @author   Ing. Jozef Saklo
 * @version  1.0
 * @date     17. December 2008
 * @revision $Id: Ext.ux.tree.TreeFilterX.js 589 2009-02-21 23:30:18Z jozo $
 * @see      <a href="http://extjs.com/forum/showthread.php?p=252709">http://extjs.com/forum/showthread.php?p=252709</a>
 *
 * @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     55489
 * @demo      http://remotetree.extjs.eu
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.tree');

/**
 * Creates new TreeFilterX
 * @constructor
 * @param {Ext.tree.TreePanel} tree The tree panel to attach this filter to
 * @param {Object} config A config object of this filter
 */
Ext.ux.tree.TreeFilterX = Ext.extend(Ext.tree.TreeFilter, {
	/**
	 * @cfg {Boolean} expandOnFilter Deeply expands startNode before filtering (defaults to true)
	 */
	 expandOnFilter:true

	// {{{
    /**
     * Filter the data by a specific attribute.
	 *
     * @param {String/RegExp} value Either string that the attribute value 
     * should start with or a RegExp to test against the attribute
     * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
     * @param {TreeNode} startNode (optional) The node to start the filter at.
     */
	,filter:function(value, attr, startNode) {

		// expand start node
		if(false !== this.expandOnFilter) {
			startNode = startNode || this.tree.root;
			var animate = this.tree.animate;
			this.tree.animate = false;
			startNode.expand(true, false, function() {

				// call parent after expand
				Ext.ux.tree.TreeFilterX.superclass.filter.call(this, value, attr, startNode);

			}.createDelegate(this));
			this.tree.animate = animate;
		}
		else {
			// call parent
			Ext.ux.tree.TreeFilterX.superclass.filter.apply(this, arguments);
		}

	} // eo function filter
	// }}}
	// {{{
    /**
     * Filter by a function. The passed function will be called with each 
     * node in the tree (or from the startNode). If the function returns true, the node is kept 
     * otherwise it is filtered. If a node is filtered, its children are also filtered.
	 * Shows parents of matching nodes.
	 *
     * @param {Function} fn The filter function
     * @param {Object} scope (optional) The scope of the function (defaults to the current node) 
     */
	,filterBy:function(fn, scope, startNode) {
		startNode = startNode || this.tree.root;
		if(this.autoClear) {
			this.clear();
		}
		var af = this.filtered, rv = this.reverse;

		var f = function(n) {
			if(n === startNode) {
				return true;
			}
			if(af[n.id]) {
				return false;
			}
			var m = fn.call(scope || n, n);
			if(!m || rv) {
				af[n.id] = n;
				n.ui.hide();
				return true;
			}
			else {
				n.ui.show();
				var p = n.parentNode;
				while(p && p !== this.root) {
					p.ui.show();
					p = p.parentNode;
				}
				return true;
			}
			return true;
		};
		startNode.cascade(f);

        if(this.remove){
           for(var id in af) {
               if(typeof id != "function") {
                   var n = af[id];
                   if(n && n.parentNode) {
                       n.parentNode.removeChild(n);
                   }
               }
           }
        }
	} // eo function filterBy
	// }}}

}); // eo extend


/**
 * AutoHistory API:
 *
 * The module AutoHistory has the following public method:
 *   recordHistEvent(id, oldVal, newVal)
 *      id     - the component ID of a ExtJS component which supports the NavState interface,
 *      oldVal - a string representing the previous navigation state
 *      newVal - a string representing the new navigation state.
 *
 * The strings can be anything the caller wants.  They will be passed back as-is.
 *
 * The caller must support the following interface:
 *
 *   getNavState(): navVal
 *      navVal - a string representing the current navigation state
 *   setNavState(navVal)
 *      navVal - a string previously passed to recordHistEvent
 */

Ext.ns('Ext.ux.RapidApp');

Ext.ux.RapidApp.HistoryInit = function() {
	Ext.History.init();
	Ext.History.on('change', Ext.ux.RapidApp.HashNav.handleHashChange);
	Ext.ux.RapidApp.HashNav.INITIALIZED = true;
}

Ext.ux.RapidApp.HashNav = {
	
	INITIALIZED: false,
	INIT_LOCATION_HASH: window.location.hash,
	INIT_TITLE: document.title,
	ignoreHashChange: false,
	
	hashpath_to_autoLoad: function(hashpath) {
		var token = hashpath;
		
		// hashpath with or without leading #
		if(hashpath.search('#') == 0) { token = hashpath.substring(1); }
		
		// valid hashpaths must start with '!/'
		if(token.search('!/') !== 0) { return null; }
		
		var url = token.substring(1); // strip leading !
		var params = {};
		
		// double ?? means base64+json encoded query string (params):
		var parts = url.split('??');
		if(parts.length > 1) {
			url = parts.shift();
			var encP = parts.join('??');
			params = Ext.decode(base64.decode(encP));
		}
		else {
			// else, single ? is a standard urlEncoded query string
			parts = url.split('?');
			if(parts.length > 1) {
				url = parts.shift();
				params = Ext.urlDecode(parts.join('?'));
			}
		}
		
		var autoLoad = {
			url: url, 
			params: params 
		};
		
		return autoLoad;
	},
	
	isParamsUrlSafe: function(params) {
		var safe = true;
		Ext.iterate(params,function(k,v){
			if(v.search && v.search('=') !== -1) { safe = false; }
		});
		return safe;
	},
	
	autoLoad_to_hashpath: function(autoLoad){
		if(Ext.isObject(autoLoad) && Ext.isString(autoLoad.url)) { 
			// We never want to see %2ff type characters (needed for chrome in certain places)
      var url = decodeURIComponent( autoLoad.url );
      
      // Ignore if url doesn't start with /:
      if(url.search('/') !== 0) { return null; }
      var hashpath = '#!' + url;

			if(Ext.ux.RapidApp.HashNav.isParamsUrlSafe(autoLoad.params)) {
				// Use standard url encoded query string:
				var encParams = autoLoad.params ? Ext.urlEncode(autoLoad.params) : '';
				if(encParams.length > 0) { 
					hashpath += '?' + encParams; 
				}
			}
			else {
				// Use special base64+json encoded query string, denoted with double '??'
				var encP = Ext.encode(autoLoad.params || {});
				if(encP !== '{}') {
					hashpath += '??' + base64.encode(encP);
				}
			}
			
			return hashpath;
		}
		return null;
	},
	
	// Set's the hashpath without doing a nav:
	setHashpath: function(load) {
		var autoLoad = Ext.isString(load) ? {url:load,params:{}} : load;
		var hashpath = Ext.ux.RapidApp.HashNav.autoLoad_to_hashpath(autoLoad);
		if(hashpath && decodeURIComponent(window.location.hash) !== decodeURIComponent(hashpath)) {
			// Git Issue #1
      //  This setting was an ugly attempt to track state, but in certain cases it
      //  is not properly reset causing a manual URL change by the user to be ignored.
      //  Furthermore, I don't *think* that this is even needed anymore because the
      //  AppTab is smarter now to do the right thing, but I am not sure. I am turning
      //  this off for now to see if it causes problems and if it doesn't I will come 
      //  back later and remove the rest of the 'ignoreHashChange' checks below
      // UPDATE: ignoreHashChange *was* very much still needed for Chrome. Without it,
      //  infinate tabs can open! -- but, this appears to be caused from improper url %
      //  encode/decode... Updated 'autoLoad_to_hashpath' below to wrap decodeURIComponent
      //  which appears to have fixed this and allowed ignoreHashChange to remain disabled.
      //  will still need to keep an eye and do more testing before removing for good...
      //Ext.ux.RapidApp.HashNav.ignoreHashChange = true;
			window.location.hash = hashpath;
		}
	},
  
  clearHashpath: function() {
    window.location.hash = '';
  },
	
	handleHashChange: function(hashpath) {
    if(Ext.ux.RapidApp.HashNav.ignoreHashChange) {
			Ext.ux.RapidApp.HashNav.ignoreHashChange = false;
			return;
		}
		
		var loadTarget = Ext.getCmp('main-load-target');
		if(!loadTarget) { return; }
		
		var autoLoad = Ext.ux.RapidApp.HashNav.hashpath_to_autoLoad(hashpath);
		if(!autoLoad) {
			// Try to reset the hashpath to the active tab
			var tab = loadTarget.getActiveTab.call(loadTarget);
			autoLoad = tab ? tab.autoLoad : null;
			return Ext.ux.RapidApp.HashNav.setHashpath(autoLoad);
		}
		
		loadTarget.loadContent({ autoLoad: autoLoad });
		Ext.ux.RapidApp.HashNav.ignoreHashChange = false;
	},
	
	/*  Ext.ux.RapidApp.HashNav.formSubmitHandler:
	 Function to be used as 'onsubmit' for any html form tag/element to
	 "submit" the form to a hashpath/loadcontent url instead of doing 
	 an actual GET/POST, directly. 
	 
	 Example:
	
		<form 
		 action="#!/main/explorer/navtree/classicdb_employees" 
		 onsubmit="return Ext.ux.RapidApp.HashNav.formSubmitHandler.apply(this,arguments);"
		>
			<label for="quick_search">Search Employees:</label>
			<input type="text" name="quick_search" />
		</form>
		
	 If 'abc123' were typed into the form/field it would load the following hashpath url:
		
		#!/main/explorer/navtree/classicdb_employees?quick_search=abc123
		
	*/
	formSubmitHandler: function(e) {

		var action = this.getAttribute('action');
		if(!action || action.search('#!/') !== 0) {
			alert("Invalid form action URL: '" + action + 
				'" - HashNav form actions must be valid hashpaths starting with \'#!/\'');
			return false;
		}
		
		var parts = action.split('?');
		if(parts.length > 2) {
			alert("Invalid form action URL: '" + action + 
				'" - multiple question-mark (?) characters are not allowed');
			return false;
		}
		
		var url = action;
		var params = {};
		
		if(parts.length > 1) {
			url = parts.shift();
			params = Ext.urlDecode(parts.join('?'));
		}
		
		for (var i = 0; i < this.elements.length; i++) {
			var name = this.elements[i].name, value = this.elements[i].value;
			if(name && value) {
				if(params[name]) {
					alert("duplicate param name '" + name + "' in HashNav form/url - not supported");
					return false;
				}
				params[name] = value;
			}
		}
		
		var hashpath = url;
		var encParams = Ext.urlEncode(params);
		if(encParams.length > 0) {
			hashpath = url + '?' + encParams;
		}
		
		window.location.hash = hashpath;
		
		// Important! the onsubmit handler *must* return false to stop the
		// normal GET/POST browser submit operation (but we also called
		// e.preventDefault() first, so this isn't also needed)
		return false;
	},
	
	updateTitle: function(title) {
		if(!title || !Ext.isString(title) || title.search(/[\w\s\-]+$/) == -1) {
			document.title = Ext.ux.RapidApp.HashNav.INIT_TITLE;
		}
		else {
			document.title = title + ' - ' + Ext.ux.RapidApp.HashNav.INIT_TITLE;
		}
	},
  
  // New: util function - converts a normal URL into a hashpath
  urlToHashPath: function(url) {
    // Return urls that already start with '#' as-is
    if(url.search('#') == 0) { return url; }
    
    //absolute: 
    var hashpath = '#!' + url;
    
    // relative:
    if(url.search('/') !== 0) {
      var parts = window.location.hash.split('/');
      if(parts[0] == '#!') {
        // If we're already at a hashnav path, use it as the base:
        parts.pop();
        parts.push(url);
        hashpath = parts.join('/');
      }
      else {
        // make absolute:
        hashpath = '#!/' + url
      }
    }
    
    return hashpath;
  }
};





/* -- Old component-id-based history code

Ext.ux.RapidApp.HistoryInit = function() {
	Ext.History.init();
	Ext.History.on('change', function(token) { Ext.ux.RapidApp.AutoHistory.handleHistChange(token); });
	Ext.ux.RapidApp.AutoHistory.installSafeguard();
};


Ext.ux.RapidApp.AutoHistory= {
	navIdx: 0,
	wrapIdx: function(idx) { return idx > 99? (idx - 100) : (idx < 0? idx + 100 : idx); },
	isForwardNav: function(oldIdx, newIdx) { var diff= this.wrapIdx(newIdx-oldIdx); return diff < 50; },
	currentNav: '',
	
	// add a fake nav event to prevent the user from backing out of the page
	installSafeguard: function() {
		this.currentNav= ''+this.navIdx+':::';
		Ext.History.add(this.currentNav);
	},
	
	// follow a nav event given to us by the application
	recordHistEvent: function(id, oldval, newval) {
		if (!newval) return;
		
		this.navIdx= this.wrapIdx(this.navIdx+1);
		this.currentNav= ''+this.navIdx+':'+id+':'+oldval+':'+newval;
		//console.log('recordHistEvent '+this.currentNav);
		
		Ext.History.add(this.currentNav);
	},
	
	performNav: function(id, newVal) {
		if (!id) return;
		if (!newVal) return;
		var target= Ext.getCmp(id);
		if (!target) return;
		//console.log('AutoHistory.performNav: '+id+'->setNav '+newVal);
		target.setNavState(newVal);
	},
	
	// respond to user back/forward navigation, but ignore ones generated by recordHistEvent
	handleHistChange: function(navTarget) {
		if (!navTarget) {
			if (this.currentNav) {
				var parts= this.currentNav.split(':');
				this.performNav(parts[1], parts[2]);
			}
			this.installSafeguard();
			return;
		}
		
		// ignore events caused by recordHistEvent
		if (navTarget != this.currentNav) {
			//console.log('AutoHistory.handleHistChange: '+this.currentNav+' => '+navTarget);
			var parts= navTarget.split(':');
			var navTargetIdx= parseInt(parts[0]);
			if (this.isForwardNav(this.navIdx, navTargetIdx)) {
				this.performNav(parts[1], parts[3]);
			}
			else {
				var parts= this.currentNav.split(':');
				this.performNav(parts[1], parts[2]);
			}
			this.currentNav= navTarget;
			this.navIdx= navTargetIdx;
		}
	}
};


Ext.override(Ext.TabPanel, {
	initComponent_orig: Ext.TabPanel.prototype.initComponent,
	initComponent: function() {
		//this.constructor.prototype.initComponent.call(this);
		this.initComponent_orig.apply(this,arguments);
		this.internalTabChange= 0;
		
		this.on('beforetabchange', function(tabPanel, newTab, currentTab) {
			if (newTab && currentTab && !this.internalTabChange) {
				Ext.ux.RapidApp.AutoHistory.recordHistEvent(tabPanel.id, currentTab.id, newTab.id);
			}
			this.internalTabChange= 0;
			return true;
		});
	},
	setNavState: function(navVal) {
		var newTab= Ext.getCmp(navVal);
		if (newTab) {
			this.internalTabChange= 1;
			this.setActiveTab(newTab);
		}
	},
	getNavState: function() { return this.getActiveTab()? this.getActiveTab().id : ""; }
});

*/
Ext.ns('Ext.ux.RapidApp.AppStoreForm2');

Ext.ux.RapidApp.AppStoreForm2.FormPanel = Ext.extend(Ext.form.FormPanel,{

	// Defaults:
	closetab_on_create: true,
	bodyCssClass: 'panel-borders',
	monitorValid: true,
	trackResetOnLoad: true, // <-- for some reason this default doesn't work and has to be set in the constructor
	frame: true,
	autoScroll: true,
	addBtnId: null,
	saveBtnId: null,

	initComponent: function() {
		this.store.formpanel = this;
		Ext.ux.RapidApp.AppStoreForm2.FormPanel.superclass.initComponent.apply(this,arguments);
	},
	getAddBtn: function() {
		if (this.addBtnId) { return Ext.getCmp(this.addBtnId); }
		var tbar= this.getTopToolbar();
		if (tbar) { return tbar.getComponent("add-btn"); }
		return null;
	},
	getSaveBtn: function() {
		if (this.saveBtnId) { return Ext.getCmp(this.saveBtnId); }
		var tbar= this.getTopToolbar();
		if (tbar) { return tbar.getComponent("save-btn"); }
		return null;
	}
});
Ext.reg('appstoreform2', Ext.ux.RapidApp.AppStoreForm2.FormPanel);

Ext.ux.RapidApp.AppStoreForm2.reload_handler = function(cmp) {
	var fp= ('appstoreform_id' in cmp)? Ext.getCmp(cmp.appstoreform_id) : cmp.findParentByType('appstoreform2');
	//var fp = cmp.findParentByType('appstoreform2');
	fp.store.reload();
};


Ext.ux.RapidApp.AppStoreForm2.save_handler = function(cmp) {
	var fp= ('appstoreform_id' in cmp)? Ext.getCmp(cmp.appstoreform_id) : cmp.findParentByType('appstoreform2');
	var form = fp.getForm();
	var store = fp.store;
	var record = store.getAt(0);
	record.beginEdit();
	form.updateRecord(record);
	record.endEdit();
	return store.save();
};

Ext.ux.RapidApp.AppStoreForm2.add_handler = function(cmp) {
	var fp= ('appstoreform_id' in cmp)? Ext.getCmp(cmp.appstoreform_id) : cmp.findParentByType('appstoreform2');
	var form = fp.getForm();
	var store = fp.store;
	
	store.rejectChanges();
	store.removeAll();
	
	var form_data = form.getFieldValues();
	var store_fields = [];
	Ext.iterate(form_data,function(key,value){
		store_fields.push({name: key});
	});
	var record_obj = Ext.data.Record.create(store_fields);
	var record = new record_obj;
	if (record) Ext.log("record created...");
	record.beginEdit();
	if (form.updateRecord(record)) Ext.log("record updated with form...");
	record.endEdit();
	store.add(record);
	return store.save();
};

Ext.ux.RapidApp.AppStoreForm2.clientvalidation_handler = function(FormPanel, valid) {
	var save_btn = FormPanel.getSaveBtn();
	var add_btn = FormPanel.getAddBtn();
	
	if(this.forceInvalid) {
		if (save_btn && !save_btn.disabled) save_btn.disable();
		if (add_btn && !add_btn.disabled) add_btn.disable();
		return;
	}
	
	if (valid && FormPanel.getForm().isDirty()) {
		if(save_btn) save_btn.enable();
		if(add_btn) add_btn.enable();
	} else {
		if (save_btn && !save_btn.disabled) save_btn.disable();
		if (add_btn && !add_btn.disabled) add_btn.disable();
	}
};

Ext.ux.RapidApp.AppStoreForm2.afterrender_handler = function(FormPanel) {
	new Ext.LoadMask(FormPanel.getEl(),{
		msg: "StoreForm Loading...",
		store: FormPanel.store
	});
	FormPanel.store.load();
};

Ext.ux.RapidApp.AppStoreForm2.store_load_handler = function(store,records,options) {

	var form = store.formpanel.getForm();
	var Record = records[0];
	if(!Record) return;
	form.loadRecord(Record);
	store.setBaseParam("orig_params",Ext.util.JSON.encode(Record.data));
}


Ext.ux.RapidApp.AppStoreForm2.store_create_handler = function(store,action,result,res,rs) {
	if(action != "create"){ return; }

	var panel = store.formpanel;
	if(!res.raw.loadCfg && !panel.closetab_on_create) { return; }

	// get the current tab:
	var tp, tab;
	if(panel.closetab_on_create) {
		tp = panel.findParentByType("apptabpanel");
		if (tp) { tab = tp.getActiveTab(); }
	}

	// Automatically load "loadCfg" if it exists in the response:
	if(res.raw.loadCfg) {
		var loadTarget = Ext.ux.RapidApp.AppTab.findParent_loadTarget(panel);
		if (loadTarget) { loadTarget.loadContent(res.raw.loadCfg); }
	}

	// close the tab:
	if(panel.closetab_on_create && tp && tab) {
		tp.remove(tab);
	}
}


Ext.ux.RapidApp.AppStoreForm2.save_and_close = function(fp) {
	var store = fp.store;

	var tp = fp.findParentByType("apptabpanel");
	var tab = tp.getActiveTab();

	var add_btn = fp.getAddBtn();

	// if both add_btn and closetab_on_create are true, then we don't have to
	// add a listener to the store because it should already have one that will
	// close the active tab:
	if (! add_btn || ! fp.closetab_on_create) {
		store.on('write',function() { tp.remove(tab); });
	}

	// find either add-btn or save-btn (they shouldn't both exist):
	var btn = add_btn? add_btn : fp.getSaveBtn();
	
	// call the button's handler directly:
	return btn.handler(btn);
}





/*
 Refactored based on example here (2011-05-10 by HV):
 http://www.sencha.com/forum/showthread.php?128164-Set-value-on-a-searching-combo-box-SOLVED&highlight=combo+query+type
*/
Ext.ns('Ext.ux.RapidApp.AppCombo2');
Ext.ux.RapidApp.AppCombo2.ComboBox = Ext.extend(Ext.form.ComboBox,{
	
	allowSelectNone: false,
	selectNoneLabel: '(None)',
	selectNoneCls: 'ra-combo-select-none',
	selectNoneValue: null,
	
	initComponent: function() {
		Ext.ux.RapidApp.AppCombo2.ComboBox.superclass.initComponent.call(this);
		
		if (this.baseParams) {
			Ext.apply(this.getStore().baseParams,this.baseParams);
		}
	},
	
	lastValueClass: '',
	
	nativeSetValue: function(v) {
		if (this.valueCssField) {
			var record = this.findRecord(this.valueField, v);
			if (record) {
				var addclass = record.data[this.valueCssField];
				if (addclass) { 
					this.el.replaceClass(this.lastValueClass,addclass);
					this.lastValueClass = addclass;
				}
			}
		}
		return Ext.form.ComboBox.prototype.setValue.apply(this,arguments);
	},
	
	setValue: function(v){
	
    this.apply_field_css();
	
    if (!v || v == '') { return this.nativeSetValue(v); }
		
		this.getStore().baseParams['valueqry'] = v;
		var combo = this;
		if(this.valueField){
			var r = this.findRecord(this.valueField, v);
			if (!r) {
				var data = {}
				data[this.valueField] = v
				this.store.load({
					params:data,
					callback:function(){
						var Store = combo.getStore();
						if(Store){
							delete Store.baseParams['valueqry'];
						}
						combo.nativeSetValue(v);
					}
				})   
			} else return combo.nativeSetValue(v);
		} else combo.nativeSetValue(v);
	},
	
	apply_field_css: function() {
		if (this.focusClass) {
			this.el.addClass(this.focusClass);
		}
		if (this.value_addClass) {
			this.el.addClass(this.value_addClass);
		}
	},
	
	onLoad: function() {
		if(this.allowSelectNone && !this.hasNoneRecord()) {
			this.insertNoneRecord();
		}
		return Ext.ux.RapidApp.AppCombo2.ComboBox.superclass.onLoad.apply(this,arguments);
	},
	
	hasNoneRecord: function() {
		var store = this.getStore();
		var Record = store.getAt(0);
		return (Record && Record.isNoneRecord);
	},
	
	getSelectNoneLabel: function() {
		return ! this.selectNoneCls
			? this.selectNoneLabel
				: '<span class="' + this.selectNoneCls + '">' + 
					this.selectNoneLabel + 
				'</span>';
	},
	
	insertNoneRecord: function(){
		var store = this.getStore();
		var data = {};
		data[this.valueField] = this.selectNoneValue;
		data[this.displayField] = this.getSelectNoneLabel();
		var noneRec = new store.recordType(data);
		noneRec.isNoneRecord = true;
		store.insert(0,noneRec);
	},
	
	// Record used as the target record for the select operation *after* '(None)' has
	// been selected from the dropdown list. This is needed because we don't want "(None)"
	// shown in the field (we want it to be empty). This record is never actually added 
	// to the store
	getEmptyValueRecord: function() {
		if(!this.emptyValueRecord) {
			var store = this.getStore();
			var data = {};
			data[this.valueField] = this.selectNoneValue;
			data[this.displayField] = this.selectNoneValue; //<-- this is where we differ from None Record
			this.emptyValueRecord = new store.recordType(data);
		}
		return this.emptyValueRecord;
	},
	
	onSelect: function(Record,index) {
		if(this.allowSelectNone && Record.isNoneRecord) {
			var emptyRec = this.getEmptyValueRecord();
			return Ext.ux.RapidApp.AppCombo2.ComboBox.superclass.onSelect.call(this,emptyRec,index);
		}
		return Ext.ux.RapidApp.AppCombo2.ComboBox.superclass.onSelect.apply(this,arguments);
	}

});
Ext.reg('appcombo2', Ext.ux.RapidApp.AppCombo2.ComboBox);



// TODO: Make this the parent class of and merge with AppCombo2 above:
Ext.ux.RapidApp.AppCombo2.CssCombo = Ext.extend(Ext.form.ComboBox,{
	
	lastValueClass: '',
	
	clearCss: false,
	
	setValue: function(v) {

		if (this.valueCssField) {
			var record = this.findRecord(this.valueField, v);
			if (record) {
				var addclass = record.data[this.valueCssField];
				if (addclass && this.el) { 
					this.el.replaceClass(this.lastValueClass,addclass);
					this.lastValueClass = addclass;
				}
			}
			else {
				if(this.clearCss) {
					this.el.removeClass(this.lastValueClass);
				}
			}
		}
		return Ext.form.ComboBox.prototype.setValue.apply(this,arguments);
	}
});



Ext.ux.RapidApp.AppCombo2.IconCombo = Ext.extend(Ext.ux.RapidApp.AppCombo2.CssCombo,{
	mode: 'local',
	triggerAction: 'all',
	editable: false,
	value_list: false,
	valueField: 'valueField',
	displayField: 'displayField',
	valueCssField: 'valueCssField',
	cls: 'with-icon',
	clearCss: true,
	initComponent: function() {
		if (this.value_list) {
			var data = [];
			Ext.each(this.value_list,function(item,index){
				if(Ext.isArray(item)) {
					data.push([item[0],item[1],item[2]]);
				}
				else {
					data.push([item,item,item]);
				}
			});
			this.store = new Ext.data.ArrayStore({
				fields: [
					this.valueField,
					this.displayField,
					this.valueCssField
				],
				data: data
			});
		}
		
		this.tpl = 
			'<tpl for=".">' +
				'<div class="x-combo-list-item">' +
					'<div class="with-icon {' + this.valueCssField + '}">' +
						'{' + this.displayField + '}' +
					'</div>' +
				'</div>' +
			'</tpl>';
		
		Ext.ux.RapidApp.AppCombo2.IconCombo.superclass.initComponent.apply(this,arguments);
	}
});
Ext.reg('ra-icon-combo',Ext.ux.RapidApp.AppCombo2.IconCombo);

// TODO: remove Ext.ux.MultiFilter.StaticCombo and reconfigure MultiFilter
// to use this here as a general purpose component
Ext.ux.RapidApp.StaticCombo = Ext.extend(Ext.ux.RapidApp.AppCombo2.CssCombo,{
	mode: 'local',
	triggerAction: 'all',
	editable: false,
	forceSelection: true,
	value_list: false, //<-- set value_list to an array of the static values for the combo dropdown
	valueField: 'valueField',
	displayField: 'displayField',
	valueCssField: 'valueCssField',
	itemStyleField: 'itemStyleField',
	useMenuList: false,
	initComponent: function() {
		if (this.value_list || this.storedata) {
			if(!this.storedata) {
				this.storedata = [];
				Ext.each(this.value_list,function(item,index){
					if(Ext.isArray(item)) {
						this.storedata.push([item[0],item[1],item[2],item[3]]);
					}
					else {
						// x-null-class because it has to be something for replaceClass to work
						this.storedata.push([item,item,'x-null-class','']);
					}
				},this);
			}
			this.store = new Ext.data.ArrayStore({
				fields: [
					this.valueField,
					this.displayField,
					this.valueCssField,
					this.itemStyleField
				],
				data: this.storedata
			});
			
			this.tpl = 
				'<tpl for=".">' +
					'<div class="x-combo-list-item {' + this.valueCssField + '}" ' +
						'style="{' + this.itemStyleField + '}">' +
						'{' + this.displayField + '}' +
					'</div>' +
				'</tpl>';
		}
		Ext.ux.RapidApp.StaticCombo.superclass.initComponent.apply(this,arguments);
		
		// New custom funtionality replaces the normal dropdown with a menu.
		// TODO: make this a general plugin. The reason this hasn't been done yet
		// is because there is no functionality to handle store event/changes, so
		// this only works with static value (i.e. StaticCombo)
		if(this.useMenuList) {
			var combo = this;
			var orig_initList = this.initList;
			this.initList = function() {
				if(!combo.list) {
					orig_initList.call(combo);
					combo.initMenuList.call(combo);
				}
			};
			// pre-init menu for performance:
			this.getMenuList();
		}
	},
	
	initMenuList: function () {
		
		this.expand = function() {
			var menu = this.getMenuList();
			
			// Have to track expand status manually so clicking the combo shows
			// and then hides the menu (vs show it over and over since menus auto
			// hide themselves)
			if(this.expandFlag) {
				this.expandFlag = false;
				return;
			}
			
			this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
			menu.showAt(this.list.getXY());
			this.expandFlag = true;
		};
		
		// Reset the expand flag when the field blurs:
		this.on('blur',function(){ this.expandFlag = false; },this);
		
	},
	
	getMenuList: function() {
		if(!this.menuList) {
			
			var items = [];
			this.store.each(function(record,i){
				items.push({
					text: record.data[this.displayField],
					value: record.data[this.displayField],
					scope: this,
					handler: this.onSelect.createDelegate(this,[record,i])
				});
			},this);
			
			var menuCfg = {
				items: items,
				maxHeight: this.maxHeight,
				plugins: ['menu-filter'],
				autoFocusFilter: true
			};
			
			// Skip the filter if there are fewer than 5 items:
			if(items.length < 5) { delete menuCfg.plugins; }
			
			this.menuList = new Ext.menu.Menu(menuCfg);
			
			this.menuList.on('show',function(menu){
				menu.setPosition(this.list.getXY());
				this.updateItemsStyles();
			},this);
			
		}
		return this.menuList;
	},
	
	updateItemsStyles: function(){
		var Menu = this.menuList;
		var cur_val = this.getValue();
		Menu.items.each(function(mitem) {
			if(typeof mitem.value == "undefined") { return; }
			var el = mitem.getEl();
			if(mitem.value == cur_val) {
				el.setStyle('font-weight','bold');
				mitem.setIconClass('ra-icon-checkbox-yes');
			}
			else {
				el.setStyle('font-weight','normal');
				mitem.setIconClass('');
			}
		},this);
	}
	
});
Ext.reg('static-combo',Ext.ux.RapidApp.StaticCombo);


// Like Ext.form.DisplayField but doesn't disable validation stuff:
Ext.ux.RapidApp.UtilField = Ext.extend(Ext.form.TextField,{
	//validationEvent : false,
	//validateOnBlur : false,
	defaultAutoCreate : {tag: "div"},
	/**
	* @cfg {String} fieldClass The default CSS class for the field (defaults to <tt>"x-form-display-field"</tt>)
	*/
	fieldClass : "x-form-display-field",
	/**
	* @cfg {Boolean} htmlEncode <tt>false</tt> to skip HTML-encoding the text when rendering it (defaults to
	* <tt>false</tt>). This might be useful if you want to include tags in the field's innerHTML rather than
	* rendering them as string literals per the default logic.
	*/
	htmlEncode: false,

	// private
	//initEvents : Ext.emptyFn,

	//isValid : function(){
	//	return true;
	//},

	//validate : function(){
	//	return true;
	//},

	getRawValue : function(){
		var v = this.rendered ? this.el.dom.innerHTML : Ext.value(this.value, '');
		if(v === this.emptyText){
			v = '';
		}
		if(this.htmlEncode){
			v = Ext.util.Format.htmlDecode(v);
		}
		return v;
	},

	getValue : function(){
		return this.getRawValue();
	},

	getName: function() {
		return this.name;
	},

	setRawValue : function(v){
		if(this.htmlEncode){
			v = Ext.util.Format.htmlEncode(v);
		}
		return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v);
	},

	setValue : function(v){
		this.setRawValue(v);
		return this;
	}
});

//Ext.ux.RapidApp.ClickActionField = Ext.extend(Ext.form.DisplayField,{
Ext.ux.RapidApp.ClickActionField = Ext.extend(Ext.ux.RapidApp.UtilField,{
	
	actionOnShow: false,
	
	actionFn: Ext.emptyFn,
	
	nativeGetValue: Ext.form.DisplayField.prototype.getValue,
	nativeSetValue: Ext.form.DisplayField.prototype.setValue,
	
	initComponent: function() {
		Ext.ux.RapidApp.ClickActionField.superclass.initComponent.call(this);
		this.addEvents( 'select' );
		this.on('select',this.onSelectMe,this);
		this.on('render',this.onShowMe,this);
		this.on('show',this.onShowMe,this);
	},
	
	onSelectMe: function() {
		this.actionRunning = false;
	},
	
	onShowMe: function() {
		this.applyElOpts();
		
		if(this.actionOnShow && (this.nativeGetValue() || !this.isInForm())) {
			// If there is no value yet *and* we're in a form, don't call the action
			// We need this because in the case of a form we don't want the action to
			// be called on show, we want it called on click. In the case of an edit 
			// grid and AppDV, we want to run the action on show because on show in
			// that context happens after we've clicked to start editing
			this.callActionFn.defer(10,this);
		}
	},
	
	isInForm: function() {
		if(this.ownerCt) {
			
			// Special, if in MultiFilter (TODO: clean this up and find a more generaalized
			// way to detect this stuff without having to create custom tests for each different
			// scenario/context!
			if(Ext.isObject(this.ownerCt.datafield_cnf)) { return true; }
			
			var xtype = this.ownerCt.getXType();
			if(xtype == 'container' && this.ownerCt.initialConfig.ownerCt) {
				// special case for compositfield, shows wrong xtype
				xtype = this.ownerCt.initialConfig.ownerCt.getXType();
			}
			if(!xtype) { return false; }
			// any xtype that contains the string 'form' or 'field':
			if(xtype.search('form') != -1 || xtype.search('field') != -1) {
				return true;
			}
		}
		return false;
	},
	
	callActionFn: function() {
		if(this.actionRunning || this.disabled) { return; }
		this.actionRunning = true;
		this.actionFn.apply(this,arguments);
	},
	
	applyElOpts: function() {
		var el = this.getEl();
		if(el && !el.ElOptsApplied) {
			el.applyStyles('cursor:pointer');
			// Click on the Element:
			el.on('click',this.onClickMe,this);
			el.ElOptsApplied = true;
		}
	},
	
	onClickMe: function(e) {
		this.callActionFn.defer(10,this,arguments);
	},
	
	// Make us look like a combo with an 'expand' function:
	expand: function(){
		this.callActionFn.defer(10,this);
	}
});
Ext.reg('click-action-field',Ext.ux.RapidApp.ClickActionField);

Ext.ux.RapidApp.ClickCycleField = Ext.extend(Ext.ux.RapidApp.ClickActionField,{
	
	value_list: [],
	
	// cycleOnShow: if true, the the value is cycled when the field is shown
	cycleOnShow: false,
	
	fieldClass: 'x-form-field x-grid3-hd-inner no-text-select',
	
	initComponent: function() {
		Ext.ux.RapidApp.ClickCycleField.superclass.initComponent.call(this);
		
		this.actionOnShow = this.cycleOnShow;
		
		var map = {};
		var indexmap = {};
		var itemlist = [];
		Ext.each(this.value_list,function(item,index) {
			
			var value, text, cls; 
			if(Ext.isArray(item)) {
				value = item[0];
				text = item[1] || name;
				cls = item[2];
			}
			else {
				value = item;
				text = item;
			}
			
			map[value] = {
				value: value,
				text: text,
				cls: cls,
				index: index
			};
			indexmap[index] = map[value];
			itemlist.push(map[value]);
			
		},this);
		
		this.valueMap = map;
		this.indexMap = indexmap;
		this.valueList = itemlist;
	},
	
	setValue: function(v) {
		
		this.dataValue = v;
		var renderVal = v;
		if(this.valueMap[v]) { 
			var itm = this.valueMap[v];
      var text = itm.text || v;
      // New: always render with an icon (related to Github Issue #30)
      var icon_cls = itm.cls || 'ra-icon-bullet-arrow-down';
      renderVal = '<div class="with-icon ' + icon_cls + '">' + text + '</div>';
		}
		return this.nativeSetValue(renderVal);
	},
	
	getValue: function() {
		if(typeof this.dataValue !== "undefined") {
			return this.dataValue;
		}
		return this.nativeGetValue();
	},

	getCurrentIndex: function(){
		var v = this.getValue();
		var cur = this.valueMap[v];
		if(!cur) { return null; }
		return cur.index;
	},
	
	getNextIndex: function() {
		var cur = this.getCurrentIndex();
		if(cur == null) { return 0; }
		var next = cur + 1;
		if(this.indexMap[next]) { return next; }
		return 0;
	},
	
	actionFn: function() {
		
		var nextIndex = this.getNextIndex();
		var next = this.indexMap[nextIndex];
		if(typeof next == "undefined") { return; }
		
		return this.selectValue(next.value);
	},
	
	selectValue: function(v) {
		var itm = this.valueMap[v];
		if(typeof itm == "undefined" || !this.el.dom) { return; }

		var ret = this.setValue(itm.value);

		if(ret) { this.fireEvent('select',this,itm.value,itm.index); }
		return ret;
	}
	
});
Ext.reg('cycle-field',Ext.ux.RapidApp.ClickCycleField);

Ext.ux.RapidApp.ClickMenuField = Ext.extend(Ext.ux.RapidApp.ClickCycleField,{
	
	header: null,
	
	// cycleOnShow: if true, the the value is cycled when the field is shown
	menuOnShow: false,
	
	initComponent: function() {
		Ext.ux.RapidApp.ClickMenuField.superclass.initComponent.call(this);
		
		this.actionOnShow = this.menuOnShow;
	},
	
	updateItemsStyles: function(){
		var Menu = this.getMenu();
		var cur_val = this.getValue();
		Menu.items.each(function(mitem) {
			if(typeof mitem.value == "undefined") { return; }
			var el = mitem.getEl();
			if(mitem.value == cur_val) {
				el.addClass('menu-field-current-value');
			}
			else {
				el.removeClass('menu-field-current-value');	
			}
			
			//console.log(mitem.text);
		},this);
		
	},
	
	getMenu: function() {
		if(!this.clickMenu) {
			
			var cnf = {
				items: []
			};
			
			if(this.header) {
				cnf.items = [
					{
						canActivate: false,
						iconCls : 'ra-icon-bullet-arrow-down',
						style: 'font-weight:bold;color:#333333;cursor:auto;padding-right:5px;',
						text: this.header + ':',
						hideOnClick: true
					},
					{ xtype: 'menuseparator' }
				];
				
			}
			
			Ext.each(this.valueList,function(itm) {
				var menu_item = {
					text: itm.text,
					value: itm.value,
					handler: function(){
						//we just set the value. Hide is automatically called which will
						//call selectValue, which will get the new value we're setting here
						this.setValue(itm.value);
					},
					scope:this
				}
				
				if(itm.cls) { menu_item.iconCls = 'with-icon ' + itm.cls; }
				
				cnf.items.push(menu_item);
			},this);
			
			this.clickMenu = new Ext.menu.Menu(cnf);
			
			/*************************************************/
			/* TODO: fixme (see below)  */
			this.clickMenu.on('beforehide',function(){ 
				if (!this.hideAllow) {
					this.hideAllow = true;
					var func = function() {
						// The hide only proceeds if hideAllow is still true.
						// If show got called, it will be set back to false and
						// the hide will not happen. This is to solve a race 
						// condition where hide gets called before show. That isn't
						// the *real* hide. Not sure why this happens
						if(this.hideAllow) { this.clickMenu.hide(); }
					}
					func.defer(50,this);
					return false; 
				}
			},this);
			
			this.clickMenu.on('show',function(){
				this.hideAllow = false;
			},this);
			
			this.clickMenu.on('hide',function(){
				if(this.hidden){ return; }
				//if(!this.isVisible()){ return; }
				this.selectValue(this.getValue());
			},this);
			/*************************************************/
			
			this.clickMenu.on('show',this.updateItemsStyles,this);
			
		}
		return this.clickMenu;
	},
	
	actionFn: function(e) {
		var el = this.getEl();
		var pos = [0,0];
		if(el){ 
			pos = el.getXY();
		}
		else if(e && e.getXY) { pos = e.getXY(); }
		
		// TODO: sometimes it just fails to get the position! why?!
		if(pos[0] <= 0) {
			pos = this.getPosition(true);
			//console.dir(this);
		}
		
		var Menu = this.getMenu();
		
		Menu.showAt(pos);
		this.ignoreHide = false;
	}
	
});
Ext.reg('menu-field',Ext.ux.RapidApp.ClickMenuField);




Ext.ux.RapidApp.CasUploadField = Ext.extend(Ext.ux.RapidApp.ClickActionField,{
	
	// TODO
	
	
	initComponent: function() {
		Ext.ux.RapidApp.CasUploadField.superclass.initComponent.call(this);
		
	}
	
});
Ext.reg('cas-upload-field',Ext.ux.RapidApp.CasUploadField);


Ext.ux.RapidApp.CasImageField = Ext.extend(Ext.ux.RapidApp.CasUploadField,{
	
	// init/default value:
	value: '<div style="color:darkgray;">(select image)</div>',
	
	uploadUrl: '/simplecas/upload_image',
	
	maxImageWidth: null,
	maxImageHeight: null,
	
	resizeWarn: true,
	
	minHeight: 2,
	minWidth: 2,
	
	getUploadUrl: function() {
		url = this.uploadUrl;
		if(this.maxImageHeight && !this.maxImageWidth) {
			throw("Fatal: maxImageWidth must also be specified when using maxImageHeight.");
		}
		if(this.maxImageWidth) { 
			url += '/' + this.maxImageWidth; 
			if(this.maxImageHeight) { url += '/' + this.maxImageHeight; }
		}
		return url;
	},
	
	formUploadCallback: function(form,res) {
		var img = Ext.decode(res.response.responseText);
		
		if(this.resizeWarn && img.resized) {
			Ext.Msg.show({
				title:'Notice: Image Resized',
				msg: 
					'The image has been resized by the server.<br><br>' +
					'Original Size: <b>' + img.orig_width + 'x' + img.orig_height + '</b><br><br>' +
					'New Size: <b>' + img.width + 'x' + img.height + '</b>'
				,
				buttons: Ext.Msg.OK,
				icon: Ext.MessageBox.INFO
			});
		}
    else if (this.resizeWarn && img.shrunk) {
      Ext.Msg.show({
        title:'Notice: Oversized Image Shrunk',
        msg: 
          'The image is oversized and has been pre-shrunk for display <br>' +
          'purposes (however, you can click/drag it larger).<br><br>' +
          'Actual Size: <b>' + img.orig_width + 'x' + img.orig_height + '</b><br><br>' +
          'Displayed Size: <b>' + img.width + 'x' + img.height + '</b>'
        ,
        buttons: Ext.Msg.OK,
        icon: Ext.MessageBox.INFO
      });
    }
		
		img.link_url = '/simplecas/fetch_content/' + img.checksum + '/' + img.filename;
		
		if(!img.width || img.width < this.minWidth) { img.width = this.minWidth; }
		if(!img.height || img.height < this.minHeight) { img.height = this.minHeight; }
		var img_tag = 
			'<img alt="\<img: ' + img.filename + '\>" src="' + img.link_url + 
				'" width=' + img.width + ' height=' + img.height + 
				' style="background-color:yellow;"' +
			'>';
		this.setValue(img_tag);
		this.onActionComplete();
	},
	
	onActionComplete: function() {
		this.fireEvent.defer(50,this,['select']);
	},
	
	actionFn: function(){
		
		var upload_field = {
			xtype: 'fileuploadfield',
			emptyText: 'Select image',
			fieldLabel:'Select Image',
			name: 'Filedata',
			buttonText: 'Browse',
			width: 300
		};
		
		var fieldset = {
			style: 'border: none',
			hideBorders: true,
			xtype: 'fieldset',
			labelWidth: 80,
			border: false,
			items:[ upload_field ]
		};
		
		Ext.ux.RapidApp.WinFormPost.call(this,{
			title: 'Insert Image',
			width: 440,
			height:140,
			url: this.getUploadUrl(),
			useSubmit: true,
			fileUpload: true,
			fieldset: fieldset,
			success: this.formUploadCallback,
			cancelHandler: this.onActionComplete.createDelegate(this)
		});
	}
	
});
Ext.reg('cas-image-field',Ext.ux.RapidApp.CasImageField);


// increase from the default 9000 to prevent editor fields from showing through
// Keep under 15000 for menus...
Ext.WindowMgr.zseed = 12000;


Ext.ux.RapidApp.DataStoreAppField = Ext.extend(Ext.ux.RapidApp.ClickActionField,{
	
	fieldClass: 'ra-datastore-app-field',
	invalidClass: 'ra-datastore-app-field-invalid',
	updatingClass: 'ra-datastore-app-field-updating',
	actionOnShow: true,
	win_title: 'Select',
	win_width: 500,
	win_height: 450,
	value: null,
	preloadAppWindow: true,
	queryResolveInterval: 50,
	
	initComponent: function() {
		Ext.ux.RapidApp.DataStoreAppField.superclass.initComponent.call(this);
    
    this.displayCache = {};
		
		if(!this.valueField || !this.displayField || this.valueField == this.displayField) {
			this.noDisplayLookups = true;
		}
		
		if(this.preloadAppWindow){
			// init the window/app in the background as soon as we're
			// rendered (but before the user has clicked/triggered the 
			// action to show the window. It will have a head start and
			// load much faster):
			this.on('render',this.getAppWindow,this);
		}
		
		// Destroy the window only when we get destroyed:
		this.on('beforedestroy',function(){
			if(this.appWindow){ this.appWindow.close(); }
			if(this.queryTask) { this.queryTask.cancel(); }
		},this);
		
		// -- Automatically hide the window if it is visible and a nav/load target
		// event happens in the main loadTarget. This can happen if, for example,
		// the user clicks an 'open' link within the grid combo to a related object
		var loadTarget = Ext.getCmp("explorer-id").getComponent("load-target");
		if(loadTarget){
			loadTarget.on('navload',function(){
				if(this.appWindow && this.appWindow.isVisible()){
					this.appWindow.hide();
				}
			},this);
		}
		// --
		
		
		//this.on('destroy',function(){	console.log('destroy (' + this.id + ')'); },this);
		//this.on('render',function(){	console.log('render (' + this.id + ')'); },this);
		
	},
	
	onActionComplete: function() {
		this.fireEvent.defer(50,this,['select']);
	},
	
	actionFn: function() {
		this.displayWindow();
	},
	
	setUpdatingClass: function() {
		if (this.rendered && !this.preventMark) {
			this.el.addClass(this.updatingClass);
		}
	},
	
	clearUpdatingClass: function() {
		if (this.rendered && !this.preventMark) {
			this.el.removeClass(this.updatingClass);
		}
	},
	
	
	// setValue should only be called from the outside (not us, we call setData) so
	// it always will be a record id and NOT a display value which we need to lookup:
	setValue: function(value) {
		this.setUpdatingClass();
		delete this.dataValue;
		var disp = this.lookupDisplayValue(value);
		return this.setData(value,disp,this.valueDirty);
	},
	
	// private
	setData: function(value,disp,dirty) {
		if(!dirty) {
			this.valueDirty = false;
			this.clearUpdatingClass();
			this.displayCache[value] = disp;
		}
		this.dataValue = value;
		return this.nativeSetValue(disp);
	},
	
	findRecordIndex: function(value) {
		var store = this.appStore;
		if(!store || !value) { return -1; }
		return store.findExact(this.valueField,value);
	},
	
	// Checks to see if the current record cache has a supplied id value (valueField)
	// and returns the associated display value if it does
	lookupDispInRecords: function(value) {
		if(this.noDisplayLookups) { 
			this.lastDispRecordsLookupsFound = true;
			return value; 
		}
		
		this.lastDispRecordsLookupsFound = false;
		
		var store = this.appStore;
		if(!store || !value) { return null; }
		
		var index = this.findRecordIndex(value);
		if(index == -1) { return null; }
		
		var Record = store.getAt(index);
		if(!Record || typeof Record.data[this.displayField] == 'undefined') {
			return null;
		}
		
		// we set this global so we don't have to rely on a return value (since maybe the value
		// should be null, should be false, etc)
		this.lastDispRecordsLookupsFound = true;
		return Record.data[this.displayField];
	},
	
	lookupDisplayValue: function(value) {
		if(!value || this.noDisplayLookups) { 
			this.valueDirty = false;
			return value;
		}
		
		// If the value is not already dirty and we already have it in our cache,
		// return the cached value:
		if(!this.valueDirty && this.displayCache[value]) {
			return this.displayCache[value];
		}
		
		delete this.lastDispRecordsLookupsFound;
		var disp = this.lookupDispInRecords(value);
		if(!this.lastDispRecordsLookupsFound) {
			this.valueDirty = true; 
			// If the value is 'dirty' we start the query resolver task:
			this.queryResolveDisplayValue();
			return value;
		}
		
		this.valueDirty = false;
		return disp;
	},
	
	getValue: function() {
		if(typeof this.dataValue !== "undefined") {
			return this.dataValue;
		}
		return this.nativeGetValue();
	},
	
	displayWindow: function() {
		this.loadPending = true;
		this.getAppWindow().show();
	},
	
	getAppWindow: function() {
		if(!this.appWindow) {
			
			// New feature: GLOBAL_add_form_onPrepare
			// function can be supplied as either a config param, OR detected in
			// the parent container. Once set, the value will be passed into the
			// add form, which will in turn be picked up by any nested 
			// DataStoreAppField components within that add form, which is then
			// passed down the chain to any depth. This is essentially a "localized"
			// global variable. This feature is needed to support an API by which
			// the configuration of a hirearchy of nested grid combos can be accessed
			// by applying a setting to the top/first in the chain. This was added
			// specifically to allow changing which fields are required and which aren't
			// via toggle in javascript in the top add form. GLOBAL_add_form_onPrepare
			// is passed the config object of the add form in the same way as
			// add_form_onPrepare.
			//var oGLOBAL = (this.ownerCt && this.ownerCt.GLOBAL_add_form_onPrepare) ?
			//	this.ownerCt.GLOBAL_add_form_onPrepare : null;
			//
			var oGLOBAL = this.findParentBy(function(parent){
				return Ext.isFunction(parent.GLOBAL_add_form_onPrepare);
			});
			
			if(oGLOBAL && !this.GLOBAL_add_form_onPrepare) {
				this.GLOBAL_add_form_onPrepare = oGLOBAL;
			}
		
			var win, field = this;
			var autoLoad = this.autoLoad || { url: this.load_url };
			
			var select_fn;
			select_fn = function(Record) {
				if(!win || !win.app){ return; }
				if(!Record) {
					var records = win.app.getSelectedRecords();
					Record = records[0];
				}
				
				if(!Record) { return; }
				
				// ------- Handle special case where the grid is editable and the user makes changes
				// that they don't save before clicking select. Save them, then re-update the field
				// in case they changed the selected field (mostly for display purposes)
				// TODO: add code to handle the exception event/code path. Also need to do the same for the
				// confirm save dialog in datastore-plus which is where this code was copied from
				var store = Record.store;
				if(store.hasAnyPendingChanges()){
					var onsave;
					onsave = function() {
						store.un('saveall',onsave);
						var value = Record.data[field.valueField], 
							disp = Record.data[field.displayField];
						field.setData(value,disp);
					};
					store.on('saveall',onsave);
					store.saveAll();
				}
				// -------
				
				var value = Record.data[field.valueField], 
					disp = Record.data[field.displayField];
				
				if(typeof value != 'undefined') {
					if(typeof disp != 'undefined') {
						field.setData(value,disp);
					}
					else {
						field.setData(value,value);
					}
					
					win.hide();
				}
			};
			
			var select_btn = new Ext.Button({
				text: '&nbsp;Select',
				width: 90,
				iconCls: 'ra-icon-selection-up-blue',
				handler: function(){ select_fn(null); },
				scope: this,
				disabled: true
			});
			
			var add_btn = new Ext.Button({
				text: '<span style="font-weight:bold;font-size:1.1em;">Add New</span>',
				iconCls: 'ra-icon-selection-add',
				handler: Ext.emptyFn,
				hidden: true
			});
			
			var buttons = [
				'->',
				select_btn,
				{ text: 'Cancel', handler: function(){ win.hide(); } }
			];
				
			if(this.allowBlank){
				buttons.unshift(new Ext.Button({
					text: 'Select None (empty)',
					iconCls: 'ra-icon-selection',
					handler: function(){
						//field.dataValue = null;
						//field.setValue(null);
						field.setData(null,null);
						win.hide();
					},
					scope: this
				}));
			}
			
			// If this is an editable appgrid, convert it to a non-editable appgrid:
			var update_cmpConfig = function(conf) {
				if(conf && conf.xtype == 'appgrid2ed') {
					// Temp turned off this override because there turned out to be cases
					// where editing in the grid combo is desired. 
					// TODO: Need to revisit this, because in general, we probably don't
					// want to assume that editing should be allowed....
					//conf.xtype = 'appgrid2';
				}
				
				// Force persist immediately on create so "Add and select" will work as
				// expected
				conf.persist_immediately.create = true;
			};
			
			var cmpConfig = {
				// Obviously this is for grids... not sure if this will cause problems
				// in the case of AppDVs
				sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
				
				// Turn off store_autoLoad (we'll be loading on show and special actions):
				store_autoLoad: false,
				
				// Don't allow delete per default
				store_exclude_buttons: [ 'delete' ],
				
				// If add is allowed, we need to make sure it uses a window and NOT a tab
				use_add_form: 'window',
				
				// Make sure this is off to prevent trying to open a new record after being created
				// for this context we select the record after it is created
				autoload_added_record: false,
				
				// Put the add_btn in the tbar (which we override):
				tbar:[add_btn,'->'],
				
				// Modify the add_form when (if) it is prepared, setting text more specific to this 
				// context than its defaults:
				add_form_onPrepare: function(cfg) {
					cfg.title = '<span style="font-weight:bold;font-size:1.2em;" class="with-icon ra-icon-selection-add">' +
						'&nbsp;Add &amp; Select New ';
					if(field.header) { cfg.title += field.header; };
					cfg.title += '</span>';
					Ext.each(cfg.items.buttons,function(btn_cfg){
						if(btn_cfg.name == 'save') {
							Ext.apply(btn_cfg,{
								text: '<span style="font-weight:bold;font-size:1.1em;">&nbsp;Save &amp; Select</span>',
								iconCls: 'ra-icon-selection-new',
								width: 150
							});
						}
					},this);
					
					if(field.GLOBAL_add_form_onPrepare) {
						cfg.GLOBAL_add_form_onPrepare = 
							cfg.GLOBAL_add_form_onPrepare || field.GLOBAL_add_form_onPrepare;
						field.GLOBAL_add_form_onPrepare.call(field,cfg);
					}
				}
				
			};
			Ext.apply(cmpConfig,this.cmpConfig || {});
			
			
			win = new Ext.Window({
				buttonAlign: 'left',
				hidden: true,
				title: this.win_title,
				layout: 'fit',
				width: this.win_width,
				height: this.win_height,
				closable: true,
				closeAction: 'hide',
				modal: true,
				hideBorders: true,
				items: {
					GLOBAL_add_form_onPrepare: this.GLOBAL_add_form_onPrepare,
					xtype: 'autopanel',
					bodyStyle: 'border: none',
					hideBorders: true,
					itemId: 'app',
					autoLoad: autoLoad,
					layout: 'fit',
					cmpListeners: {
						afterrender: function(){
            
              // If this is a grid, take over its rowdblclick event to
              // make it call the select_fn function
              if(this.hasListener('rowdblclick')) {
                // Clear all existing rowdblclick events
                this.events.rowdblclick = true;
                this.on('rowdblclick',function(grid,rowIndex,e){
                  select_fn(null);
                },this);
              }
							
							// Save references in the window and field:
							win.app = this, field.appStore = this.store;
							
							// -- New feature added to AppGrid2. Make sure that our value field
							// is requested in the 'columns' param
							if(win.app.alwaysRequestColumns) {
								win.app.alwaysRequestColumns[field.displayField] = true;
								win.app.alwaysRequestColumns[field.valueField] = true;
							}
							// --
								
							// Add the 'first_records_cond' (new DbicLink2 feature) which will
							// move matching records, in our case, the current value, to the top.
							// this should make the currently selected row ALWAYS be the first item
							// in the list (on every page, under every sort, etc):
							this.store.on('beforeload',function(store,options) {
								var cond = this.get_first_records_cond_param();
								options.params.first_records_cond = cond;
							},field);
							
							// Safe function to call to load/reload the store:
							var fresh_load_fn = function(){
								
								if(win.app.view) { win.app.view.scrollToTop(); }
								
								// manually clear the quicksearch:
								if(this.quicksearch_plugin) {
									this.quicksearch_plugin.field.setValue('');
									this.store.purgeParams(['fields','query']);
								}
								
								// manually clear any multifilters:
								if(this.multifilter) {
									delete this.store.filterdata;
									delete this.store.filterdata_frozen;
									this.multifilter.updateFilterBtn.call(this.multifilter);
								}
								
								this.store.store_autoLoad ? this.store.load(this.store.store_autoLoad) :
									this.store.load();
							};
							
							// one-off load call if the window is already visible:
							win.isVisible() ? fresh_load_fn.call(this) : false;
							
							// Reload the store every time the window is shown:
							win.on('beforeshow',fresh_load_fn,this);
							
							
							var toggleBtn = function() {
								if (this.getSelectedRecords.call(this).length > 0) {
									select_btn.setDisabled(false);
								}
								else {
									select_btn.setDisabled(true);
								}
							};
							this.on('selectionchange',toggleBtn,this);
							
							this.store.on('write',function(ds,action,result,res,record){
								// Only auto-select new record if exactly 1 record was added and is not a phantom:
								if(action == "create" && record && typeof record.phantom != 'undefined' && !record.phantom) { 
									return select_fn(record); 
								}
							},this);
							
							this.store.on('load',function(){
								
								var value = this.getValue(), disp;

								// If the value is dirty, check if this load has the Record of the current
								// value, and if it does, opportunistically update the display:
								if(this.valueDirty) {
									disp = this.lookupDisplayValue(value);
									if(this.valueDirty) {
										// If the value is still dirty, but there is an entry in the cache,
										// update the display with it, since it is still the last known/best
										// value
										var disp_cache = this.displayCache[value];
										if(disp_cache) {
											// Call setData with the 'dirty' flag on:
											this.setData(value,disp_cache,true);
										}
									}
									else {
										// If the value is no longer dirty, disp must contain the needed 
										// display value, set it:
										this.setData(value,disp);
									}
								}
								else {
									// If the value is not currently marked as dirty, still do a lookup in the
									// store to opportunistically update it, in case the value has changed on
									// the backend since the first time we fetched it:
									delete this.lastDispRecordsLookupsFound;
									disp = this.lookupDispInRecords(value);
									if(this.lastDispRecordsLookupsFound) { 
										this.setData(value,disp);
									}
								}
								
								this.loadPending = false;
								
								if(Ext.isFunction(win.app.getSelectionModel)) {
									// If the current value is in the current Record cache, try to select the
									// row in the grid
									var sm = win.app.getSelectionModel();
									var index = this.findRecordIndex(value);
									if(index != -1) { 
										sm.selectRow(index);
										if(win.app.view){
											var rowEl = new Ext.Element(win.app.view.getRow(index));
											if(rowEl) { rowEl.addClass('ra-bold-grid-row'); }
										}
									}
									else {
										sm.clearSelections();
									}
								}
							},field);
							
							// "Move" the store add button to the outer window button toolbar:
							if(this.loadedStoreButtons && this.loadedStoreButtons.add) {
								var store_add_btn = this.loadedStoreButtons.add;
								add_btn.setHandler(store_add_btn.handler);
								add_btn.setVisible(true);
								store_add_btn.setVisible(false);
							}
							
							// Disable any loadTarget that is defined. This is a hackish way to disable
							// any existing double-click open setting. TODO: do this properly
							this.loadTargetObj = null;
							
						}
					},
					cmpConfig: cmpConfig,
					update_cmpConfig: update_cmpConfig
				},
				buttons: buttons,
				listeners: {
					hide: function(){
						field.onActionComplete.call(field);
						field.validate.call(field);
						//console.log('  win: hide (' + field.id + '/' + win.id + ')');
					},
					render: function(){
						//console.log('  win: render (' + field.id + '/' + win.id + ')');
					},
					beforedestroy: function(){
						//console.log('  win: beforedestroy (' + field.id + '/' + win.id + ')');
					}
				}
			});
			
			win.render(Ext.getBody());
			
			this.appWindow = win;
		}
		
		return this.appWindow;
	},
	
	get_first_records_cond_param: function() {
		var value = this.getValue();
		var rs_cond = {};
		var colname = this.valueField;
		if(colname.search(/__/) == -1) {
			// hackish, fixme. If there is no double-underscore (aka join) we add
			// 'me.' to prevent ambiguous column error. This is very specific to DbicLink2
			colname = 'me.' + colname;
		}
		if (value) { rs_cond[colname] = value; }
		return Ext.encode(rs_cond);
	},
	
	// This task sets up a custom Ajax query task to the server to lookup the display value
	// of a given value (id) value. For simplicity the store API is not used; a custom
	// read operation is simulated. This lookup is designed to work with a DbicApp2
	// backend. The process uses Ext.util.DelayedTask to wait until the store is ready,
	// and also to wait and see if a normal read is in progress if that might be able
	// to opportunistically resolve the display value, in which case the task is cancelled.
	// Also, since this is asynchronous, it checks at the various stages of processing to
	// see if the 'dirty' status (meaning the display value isn't available yet) has been
	// resolved, in which case this task aborts at whatever stage it is at. this is very 
	// efficient....
	queryResolveDisplayValue: function(value) {
		
		var delay = this.queryResolveInterval,
			valueField = this.valueField,
			displayField = this.displayField;
		
		if(this.queryTask) { this.queryTask.cancel(); }
		
		this.queryTask = new Ext.util.DelayedTask(function(){

			if(!this.valueDirty || !this.getValue()) { return; }
			
			var store = this.appStore;
			if(!this.rendered || !store || this.loadPending) { 
				return this.queryTask.delay(delay);
			}
			
			Ext.Ajax.request({
				url: store.api.read.url,
				method: 'POST',
				params: {
					columns: Ext.encode([this.displayField,this.valueField]),
					dir: 'ASC',
					start: 0,
					limit: 1,
					no_total_count: 1,
					resultset_condition: this.get_first_records_cond_param()
				},
				success: function(response,options) {
					
					if(!this.valueDirty) { return; }
					
					var res = Ext.decode(response.responseText);
					if(res.rows) {
						var row = res.rows[0];
						if(row) {
							var val = row[valueField], disp = row[displayField];
							
							if(val == this.getValue()) {
								this.setData(val,disp);
							}
						}
					}
				},
				scope: this
			});
			
		},this);
		
		this.queryTask.delay(delay);
	}
	
});
Ext.reg('datastore-app-field',Ext.ux.RapidApp.DataStoreAppField);


Ext.ux.RapidApp.ListEditField = Ext.extend(Ext.ux.RapidApp.ClickActionField,{
	
	fieldClass: 'ra-datastore-app-field wrap-on',
	invalidClass: 'ra-datastore-app-field-invalid',
	actionOnShow: true,
	
	delimiter: ',',
	padDelimiter: false, //<-- set ', ' instead of ','
	trimWhitespace: false, //<-- must be true if padDelimiter is true
	showSelectAll: true,
	value_list: [], //<-- the values that can be set/selected
	
	initComponent: function() {
		Ext.ux.RapidApp.ListEditField.superclass.initComponent.call(this);
		
		// init
		this.getMenu();
	},
	
	onActionComplete: function() {
		this.fireEvent.defer(50,this,['select']);
	},
	
	actionFn: function(e) {
		var el = this.getEl();
		var pos = [0,0];
		if(el){ 
			pos = el.getXY();
		}
		else if(e && e.getXY) { pos = e.getXY(); }
		
		// TODO: sometimes it just fails to get the position! why?!
		if(pos[0] <= 0) {
			pos = this.getPosition(true);
			//console.dir(this);
		}
		
		this.showMenuAt(pos);
	},
	
	showMenuAt: function(pos) {
		var menu = this.getMenu();
		menu.showAt(pos);
	},
	
	setActiveList: function(list) {
		var delim = this.delimiter;
		if(this.padDelimiter) { delim += ' '; }
		return this.setValue(list.join(delim));
	},
	
	getActiveKeys: function(){
		var str = this.getValue();
		var map = {};
		var list = str.split(this.delimiter);
		Ext.each(list,function(item){
			if(this.trimWhitespace){ item = item.replace(/^\s+|\s+$/g,""); }
			map[item] = true;
		},this);
		
		this.activeKeys = map;
		return this.activeKeys
	},
	
	applyMenuSelections: function(){
		if(this.menu && this.menu.isVisible()){
			var selected = [];
			this.menu.items.each(function(item){
				if(item.checked && item.value) { 
					selected.push(item.value); 
				}
			},this);
			this.setActiveList(selected);
			this.menu.hide();
		}
	},
	
	updateMenu: function(){
		if(this.menu) {
			var selectall_item = this.menu.getComponent('select-all');
			if(selectall_item){ 
				// Reset select all to unchecked:
				selectall_item.setChecked(false); 
			}
			var keys = this.getActiveKeys();
			var all_checked = true;
			this.menu.items.each(function(item){
				if(item.value) {
					item.setChecked(keys[item.value]);
					if(!keys[item.value]) { all_checked = false; }
				}
			},this);
			
			if(selectall_item && all_checked){
				// Set the select all checkbox to true only if all items are
				// already checked:
				selectall_item.setChecked(true,false);
			}
		}
	},
	
	getSelectAllItem: function(){
		return {
			itemId: 'select-all',
			xtype: 'menucheckitem',
			text: 'Select All',
			hideOnClick: false,
			checked: false,
			listeners: {
				checkchange: {
					scope: this,
					fn: function(itm,state) {
						this.menu.items.each(function(item){
							if(item.value) { item.setChecked(state); }
						},this);
					}
				}
			}
		}
	},
	
	getValueList: function() {
		return this.value_list;
	},
	
	// Stops the last item from being unchecked (is only set as the
	// beforecheckchange item listeners if allowBlank is false)
	itemBeforeCheckHandler: function(item,checked) {
		var count = this.getCheckedCount();
		if(!checked && count == 1) { return false; }
	},
	
	getCheckedCount: function() {
		if(!this.menu && this.menu.isVisible()) { 
			return 0;
		};
		var count = 0;
		this.menu.items.each(function(item){
			if(item.value && item.checked) { count++; }
		},this);
		
		return count;
	},
	
	getMenu: function(){
		if(!this.menu) {
			var items = [];
			if(this.showSelectAll){ 
				items.push(this.getSelectAllItem(),'-'); 
			}
			
			Ext.each(this.getValueList(),function(val){
				var cnf = {
					xtype: 'menucheckitem',
					text: val,
					value: val,
					hideOnClick: false
				};
				// add listener to prevent last item from being unchecked if this
				// field is not nullable (allowBlank false):
				if(typeof this.allowBlank != 'undefined' && !this.allowBlank) {
					cnf.listeners = {
						beforecheckchange: {
							scope: this,
							fn: this.itemBeforeCheckHandler
						}
					};
				}
				items.push(cnf);
			},this);
			
			items.push('-',{
				style: 'font-weight:bold;color:#333333;',
				text: '&nbsp;OK',
				iconCls: 'ra-icon-accept',
				hideOnClick: false,
				handler: this.applyMenuSelections,
				scope: this
			});
			
			this.menu = new Ext.menu.Menu({
				items: items
			});
			
			this.menu.on('beforeshow',this.updateMenu,this);
			this.menu.on('hide',this.onActionComplete,this);
		}
		return this.menu;
	}
	
});
Ext.reg('list-edit-field',Ext.ux.RapidApp.ListEditField);





// Extends ListEditField to use a configured store to get the value list
Ext.ux.RapidApp.MultiCheckCombo = Ext.extend(Ext.ux.RapidApp.ListEditField,{
	
	initComponent: function() {
		Ext.ux.RapidApp.MultiCheckCombo.superclass.initComponent.call(this);
		this.store.on('load',this.onStoreLoad,this);
	},
	
	getMenu: function() {
		if(!this.storeLoaded) {
			// Don't allow the menu to be created before the store is loaded
			return null;
		}
		return Ext.ux.RapidApp.MultiCheckCombo.superclass.getMenu.apply(this,arguments);
	},
	
	onStoreLoad: function() {
		this.updateValueList();
		this.storeLoaded = true;
		if(this.pendingShowAt) {
			this.showMenuAt(this.pendingShowAt);
			delete this.pendingShowAt;
		}
	},
	
	updateValueList: function() {
		var value_list = [];
		this.store.each(function(Record){
			value_list.push(Record.data[this.valueField]);
		},this);
		this.value_list = value_list;
	},
	
	showMenuAt: function(pos) {
		if(!this.storeLoaded) {
			this.pendingShowAt = pos;
			return this.store.load();
		}
		return Ext.ux.RapidApp.MultiCheckCombo.superclass.showMenuAt.apply(this,arguments);
	}
	
});
Ext.reg('multi-check-combo',Ext.ux.RapidApp.MultiCheckCombo);








/*
 *
 * Taken from http://www.sencha.com/forum/showthread.php?109569-Multiline-Toolbar-Extension&highlight=HtmlEditor+css
 *
*/

Ext.Toolbar.Break = Ext.extend(Ext.Toolbar.Item, {
  render: Ext.emptyFn,
  isBreak: true
});

Ext.reg('tbbreak', Ext.Toolbar.Break);

Ext.apply(Ext.Toolbar.prototype, {

  // Override the lookupComponent code to cater for the Break Item
  lookupComponent: function(c) {
    if (Ext.isString(c)) {

      // New code
      if (c == '.') {
        c = new Ext.Toolbar.Break();

      // Existing code
      } else if (c == '-') {
        c = new Ext.Toolbar.Separator();
      } else if (c == ' ') {
        c = new Ext.Toolbar.Spacer();
      } else if (c == '->') {
        c = new Ext.Toolbar.Fill();
      } else {
        c = new Ext.Toolbar.TextItem(c);
      }
      this.applyDefaults(c);
    } else {
      if (c.isFormField || c.render) {
        c = this.createComponent(c);
      } else if (c.tag) {
        c = new Ext.Toolbar.Item({autoEl: c});
      } else if (c.tagName) {
        c = new Ext.Toolbar.Item({el:c});
      } else if (Ext.isObject(c)) {
        c = c.xtype ? this.createComponent(c) : this.constructButton(c);
      }
    }
    return c;
  },

  // Add a function for adding a Break item
  addBreak: function() {
    this.add(new Ext.Toolbar.Break());
  }
  
});

// Override existing Toolbar onLayout with enhanced layout functionality
// Overriding the function makes it available to all toolbars
Ext.apply(Ext.layout.ToolbarLayout.prototype, {

  // onLayout is the function to override
  onLayout: function(ct, target) {
    var tableIndex = 0, targetTable;
    var layout = this;

    // Function to cleanup toolbar rows
    // Was previously called once but is now called for each toolbar table
    function cleanupRows() {
      layout.cleanup(layout.leftTr);
      layout.cleanup(layout.rightTr);
      layout.cleanup(layout.extrasTr);
    }

    // Function to add a new toolbar table
    // Is called for each toolbar row
    function nextTable() {

      // Create new table if not already created (could have been added after render)
      if (!target.dom.childNodes[tableIndex]) {
        var align = ct.buttonAlign == 'center' ? 'center' : 'left';
        target.insertHtml('beforeEnd', String.format(layout.tableHTML, align));
      }

      // Focus on current table
      targetTable = Ext.fly(target.dom.childNodes[tableIndex]);

      // If second or greater table then clean up previous table
      // and add a class that adds a spacer between tables
      if (tableIndex) {
        cleanupRows();
        targetTable.addClass('x-toolbar-add-row');
      }

      // Increment table index
      tableIndex++;

      // Assign specific row handlers
      layout.leftTr   = targetTable.child('tr.x-toolbar-left-row', true);
      layout.rightTr  = targetTable.child('tr.x-toolbar-right-row', true);
      layout.extrasTr = targetTable.child('tr.x-toolbar-extras-row', true);
      layout.side = ct.buttonAlign == 'right' ? layout.rightTr : layout.leftTr;
    }

    // If running for the first time, perform necessary functionality
    if (!this.leftTr) {
      target.addClass('x-toolbar-layout-ct');
      if (this.hiddenItem == undefined) {
        this.hiddenItems = [];
      }
    }

    // Create and/or select first toolbar table
    nextTable();

    // Loop though toolbar items
    var items = ct.items.items, position = 0;
    for (var i = 0, len = items.length, c; i < len; i++, position++) {
      c = items[i];

      // If item is the new toolbar break item then...
      if (c.isBreak) {

        // ...create and/or select additional toolbar table
        nextTable();

      // Existing code...
      } else if (c.isFill) {
        this.side = this.rightTr;
        position = -1;
      } else if (!c.rendered) {
        c.render(this.insertCell(c, this.side, position));
      } else {
        if (!c.xtbHidden && !this.isValidParent(c, this.side.childNodes[position])) {
          var td = this.insertCell(c, this.side, position);
          td.appendChild(c.getPositionEl().dom);
          c.container = Ext.get(td);
        }
      }
    }

    // Clean up last toolbar table
    cleanupRows();

    this.fitToSize(target);
  }
});


Ext.ux.form.HtmlEditor.Break = function() {

  // PRIVATE

  // pointer to Ext.form.HtmlEditor
  var editor;

  // Render Toolbar Break
  function onRender() {
    editor.getToolbar().addBreak();
  }

  // PUBLIC

  return {

    // Ext.ux.form.HtmlEditor.Break.init
    // called upon instantiation
    init: function(htmlEditor) {
      editor = htmlEditor;

      // Call onRender when Toolbar rendered
      editor.on('render', onRender, this);
    }
  }
};
/*
 http://www.sencha.com/forum/showthread.php?79210-ComponentDataView-Ext-components-inside-a-dataview-or-listview
*/

Ext.ns('Ext.ux');
Ext.ux.ComponentDataView = Ext.extend(Ext.DataView, {
    defaultType: 'textfield',
    initComponent : function(){
        Ext.ux.ComponentDataView.superclass.initComponent.call(this);
        this.components = [];
    },
    refresh : function(){
        Ext.destroy(this.components);
        this.components = [];
        Ext.ux.ComponentDataView.superclass.refresh.call(this);
        this.renderItems(0, this.store.getCount() - 1);
    },
    onUpdate : function(ds, record){
        var index = ds.indexOf(record);
        if(index > -1){
            this.destroyItems(index);
        }
        Ext.ux.ComponentDataView.superclass.onUpdate.apply(this, arguments);
        if(index > -1){
            this.renderItems(index, index);
        }
    },
    onAdd : function(ds, records, index){
        var count = this.all.getCount();
        Ext.ux.ComponentDataView.superclass.onAdd.apply(this, arguments);
        if(count !== 0){
            this.renderItems(index, index + records.length - 1);
        }
    },
    onRemove : function(ds, record, index){
        this.destroyItems(index);
        Ext.ux.ComponentDataView.superclass.onRemove.apply(this, arguments);
    },
    onDestroy : function(){
        Ext.ux.ComponentDataView.onDestroy.call(this);
        Ext.destroy(this.components);
        this.components = [];
    },
    renderItems : function(startIndex, endIndex){
        var ns = this.all.elements;
        var args = [startIndex, 0];
        for(var i = startIndex; i <= endIndex; i++){
            var r = args[args.length] = [];
            for(var items = this.items, j = 0, len = items.length, c; j < len; j++){
                c = items[j].render ?
                    c = items[j].cloneConfig() :
                    Ext.create(items[j], this.defaultType);
                r[j] = c;
                if(c.renderTarget){
                    c.render(Ext.DomQuery.selectNode(c.renderTarget, ns[i]));
                }else if(c.applyTarget){
                    c.applyToMarkup(Ext.DomQuery.selectNode(c.applyTarget, ns[i]));
                }else{
                    c.render(ns[i]);
                }
                if(Ext.isFunction(c.setValue) && c.applyValue){
                    c.setValue(this.store.getAt(i).get(c.applyValue));
                    c.on('blur', function(f){
                    	this.store.getAt(this.index).data[this.dataIndex] = f.getValue();
                    }, {store: this.store, index: i, dataIndex: c.applyValue});
                }
            }
        }
        this.components.splice.apply(this.components, args);
    },
    destroyItems : function(index){
        Ext.destroy(this.components[index]);
        this.components.splice(index, 1);
    }
});
Ext.reg('compdataview', Ext.ux.ComponentDataView);
Ext.ux.RapidApp.Plugin.TemplateControllerPanel = Ext.extend(Ext.util.Observable,{

  init: function(panel) {
    this.panel = panel;
    var eventName = this.isIframe() ? 'domready' : 'afterrender';
    this.panel.on(eventName,this.attachClickListener,this);
  },
  
  isIframe: function() { return Ext.isFunction(this.panel.getFrame); },
  
  getBodyEl: function() {
    // Standard, normal panel
    var El = this.panel.getEl();
    
    // For the special ManagedIFrame case, reach into the iframe
    // and get its inner <body> element:
    if(this.isIframe()) {
      var iFrameEl = this.panel.getFrame();
      El = new Ext.Element(
        iFrameEl.dom.contentWindow.document.body
      );
    }
    
    return El;
  },
 
	attachClickListener: function() {
    this.getBodyEl().on('click',function(event,node) {
      var target = event.getTarget(null,null,true);
      var El = new Ext.Element(target);
      if (El.hasClass('edit')) {
        var tplEl = El.parent('div.ra-template');
        if (tplEl) { return this.editTplEl(tplEl); }
      }
      else if (El.hasClass('create')) {
        var tplEl = El.parent('div.ra-template');
        if (tplEl) { return this.createTplEl(tplEl); }
      }
    },this);
	},
  
  getTplElMeta: function(tplEl) {
    var metaEl = tplEl.child('div.meta');
    var meta;
    try {
      meta = Ext.decode(metaEl.dom.innerHTML);
    }
    catch(err) {
      return Ext.Msg.alert("Bad template meta data",err);
    }
    return meta;
  },
  
  editTplEl: function(tplEl) {
    var meta = this.getTplElMeta(tplEl);
    return this.editTemplate(meta);
  },
  
  editTemplate: function(meta) {
    var url = [
      this.panel.template_controller_url,
      'get', meta.name
    ].join('/');
    
    var success_fn = function(response,options) {
      this.loadEditor(meta.name,response.responseText,meta);
    };
    
    Ext.Ajax.request({
      url: url,
      method: 'GET',
      success: success_fn,
      //failure: failure_fn
      scope: this
    });
  },
  
  createTplEl: function(tplEl) {
    var meta = this.getTplElMeta(tplEl);
    return this.createTemplate(meta);
  },
  
  createTemplate: function(meta) {
    var url = [
      this.panel.template_controller_url,
      'create', meta.name
    ].join('/');
    
    var success_fn = function(response,options) {
      this.tabReload();
      this.editTemplate(meta);
    };
    
    Ext.Ajax.request({
      url: url,
      method: 'GET',
      success: success_fn,
      //failure: failure_fn
      scope: this
    });
  },
  
  tabReload: function() {
    
    // Needed to keep a reference to the ownerCt for next time:
    var ownerCt = this.ownerCt || this.panel.ownerCt;
    this.ownerCt = ownerCt;
  
    // reload() is a new feature of AutoPanel:
    return ownerCt.reload();
    
    /* This is the old way which closes and loads a new Tab: */
    //var tab = this.tab || this.panel.ownerCt;
    //var tp = tab.ownerCt;
    //if(Ext.isFunction(tp.loadContent) && Ext.isObject(tab.loadContentCnf)) {
    //  var cnf = tab.loadContentCnf;
    //  tp.remove(tab);
    //  this.tab = tp.loadContent(cnf);
    //}
  },
  
  setTemplate: function(name,content,skip_validate) {
  
    var set_url = [
      this.panel.template_controller_url,
      'set', name
    ].join('/');
    
    var params = { content: content };
    if(skip_validate) {
      params.skip_validate = 1;
    }
    
    Ext.Ajax.request({
      url: set_url,
      method: 'POST',
      params: params,
      success: function(response,options) {
        this.win.close();
        
        // Reload the tab
        this.tabReload();
        
        // TODO: reload the template element if nested template
        
      },
      failure: function(response,options) {
        if(response.status == 418) {
          Ext.Msg.show({
            title: 'Errors in template',
            msg: [
              '<br><b>Template contains errors:</b><br><br>',
              '<div class="ra-template">',
                '<div class="tpl-error">',
                  '<div class="error-msg">',
                    Ext.util.Format.nl2br(response.responseText),
                  '</div>',
                '</div>',
              '</div>',
              '<br>',
              '<b>Save anyway?</b><br>'
            ].join(''),
            buttons: Ext.Msg.YESNO,
            icon: Ext.Msg.WARNING,
            minWidth: 275,
            fn: function(button_id) {
              if(button_id == 'yes') {
                // Call again, this time with skip_validate:
                this.setTemplate(name,content,true);
              }
            },
            scope: this
          });
        }
        else {
          Ext.Msg.show({
            title: 'Error',
            msg: Ext.util.Format.nl2br(response.responseText),
            buttons: Ext.Msg.OK,
            icon: Ext.Msg.ERROR,
            minWidth: 275
          });
        }
      },
      scope: this
    });
  },
  
  deleteTemplate: function(name) {
  
    var delete_url = [
      this.panel.template_controller_url,
      'delete', name
    ].join('/');
    
    Ext.Ajax.request({
      url: delete_url,
      method: 'GET',
      scope: this,
      success: function(response,options) {
        this.win.close();
        this.tabReload();
      }
    });
  },
  
  getFormatEditorCnf: function(format) {
    if (format == 'html-snippet') {
      return {
        // Only use the HtmlEditor for html format.
        // TODO: a new HtmlEditor is badly needed. This one is pretty limited:
        xtype: 'ra-htmleditor',
        no_autosizers: true
      };
    }
    else {
      // TODO: add smart editors for various other formats, like markdown
      return {
        xtype: 'textarea',
        style: 'font-family: monospace;'
      };
    }
  },
  
  loadEditor: function(name,content,meta) {
  
    var format = meta.format;
    var fp, panel = this.panel;
		
		var saveFn = function(btn) {
			var form = fp.getForm();
			var data = form.findField('content').getRawValue();
      return this.setTemplate(name,data);
		};
    
    var deleteFn = function(btn) {
			Ext.Msg.show({
        title: 'Confirm Delete Template',
        msg: [
          '<br><b>Really delete </b><span class="tpl-name">',
          name,'</span> <b>?</b><br><br>'
        ].join(''),
        buttons: Ext.Msg.YESNO,
        icon: Ext.Msg.WARNING,
        minWidth: 350,
        fn: function(button_id) {
          if(button_id == 'yes') {
            this.deleteTemplate(name);
          }
        },
        scope: this
      });
		};
    
    // Common config:
    var editField = {
      name: 'content',
      itemId: 'content',
      fieldLabel: 'Template',
      hideLabel: true,
      value: content,
      anchor: '-0 -0'
    };
    // Format-specific config:
    Ext.apply(editField,this.getFormatEditorCnf(format));
    
    var buttons = [
      '->',
      {
        name: 'save',
        text: 'Save',
        iconCls: 'ra-icon-save-ok',
        width: 100,
        formBind: true,
        scope: this,
        handler: saveFn
      },
      {
        name: 'cancel',
        text: 'Cancel',
        handler: function(btn) {
          this.win.close();
        },
        scope: this
      }
    ];
    
    if(meta.deletable) {
      buttons.unshift({
        name: 'delete',
        text: 'Delete',
        iconCls: 'ra-icon-garbage',
        width: 100,
        formBind: true,
        scope: this,
        handler: deleteFn
      });
    }
		
		fp = new Ext.form.FormPanel({
			xtype: 'form',
			frame: true,
			labelAlign: 'right',
			
			//plugins: ['dynamic-label-width'],
			labelWidth: 160,
			labelPad: 15,
			//bodyStyle: 'padding: 10px 10px 5px 5px;',
      bodyStyle: 'padding: 0px 0px 10px 0px;',
			defaults: { anchor: '-0' },
			autoScroll: true,
			monitorValid: true,
			buttonAlign: 'left',
			minButtonWidth: 100,
			
			items: [ editField ],

			buttons: buttons
		});
  
    if(this.win) { 
      this.win.close(); 
    }
    
    this.win = new Ext.Window({
			title: ["Edit Template ('",name,"')"].join(''),
			layout: 'fit',
			width: 800,
			height: 600,
			minWidth: 400,
			minHeight: 250,
			closable: true,
			closeAction: 'close',
			modal: true,
			items: fp
		});
    
    this.win.show();
  }
	
});
Ext.preg('template-controller-panel',Ext.ux.RapidApp.Plugin.TemplateControllerPanel);




Ext.ns('Ext.ux.MultiFilter');


Ext.ux.MultiFilter.Plugin = Ext.extend(Ext.util.Observable,{

	init: function(grid) {
		this.grid = grid;
		grid.multifilter = this;
		
		grid.allow_edit_frozen = grid.allow_edit_frozen || false;
		
		this.store = grid.getStore();
		
		if(grid.init_state && grid.init_state.multifilters) {
			this.store.filterdata = grid.init_state.multifilters;
		}
		
		this.store.on('beforeload',function(store,options) {
			if(store.baseParams) {
				delete store.baseParams.multifilter;
			}
			if(store.lastOptions && store.lastOptions.params) { 
				delete store.lastOptions.params.multifilter;
			}
			if(store.filterdata || store.filterdata_frozen) {
				var multifilter = this.getMultiFilterParam();
				var multifilter_frozen = this.getMultiFilterParam(true);
				
				// Forcefully set both baseParams and lastOptions so make sure
				// no param caching is happening in the Ext.data.Store
				store.baseParams.multifilter = multifilter;
				store.lastOptions.params.multifilter = multifilter;
				store.baseParams.multifilter_frozen = multifilter_frozen;
				store.lastOptions.params.multifilter_frozen = multifilter_frozen;
				
				// this is required for very first load to see changes 
				// (not sure why this is needed beyond the above lines)
				Ext.apply(options.params, {
					multifilter: multifilter,
					multifilter_frozen: multifilter_frozen
				});
			}
			return true;
		},this);
		
		if (grid.rendered) {
			this.onRender();
		} else {
			grid.on({
				scope: this,
				single: true,
				render: this.onRender
			 });
		}
	},
	
	getMultiFilterParam: function(frozen) {
		var data = frozen ? this.store.filterdata_frozen : this.store.filterdata;
		data = data || [];
		return Ext.encode(data);
	},
	
	onRender: function() {
	
		var grid = this.grid;

		this.filtersBtn = new Ext.Button({
			text: 'Filters',
			handler: function(btn) {
				var win = grid.multifilter.showFilterWindow();
			},
			hidden: grid.hide_multifilter_button ? true: false
		});
		
		this.updateFilterBtn();
		
		var add_to_cmp = this.grid.getBottomToolbar() || this.grid.getTopToolbar();
		if(add_to_cmp && !grid.hide_filtersBtn) { 
			if(!this.filtersBtn.hidden){
				add_to_cmp.add('-');
			}
			add_to_cmp.add(this.filtersBtn);
		};
	},
	
	setFields: function() {
		var fields = [];
		
		//var columns = this.grid.getColumnModel().config;
		var columns = this.grid.initialConfig.columns;
		
		if(! this.grid.no_multifilter_fields) { this.grid.no_multifilter_fields = {}; }
		Ext.each(columns,function(column) {
			
			if(Ext.isObject(column.editor)) { column.rel_combo_field_cnf = column.editor; }
			if(column.rel_combo_field_cnf && column.rel_combo_field_cnf.store) { 
				column.rel_combo_field_cnf.store.autoDestroy = false; 
			}
			
			if (! this.grid.no_multifilter_fields[column.dataIndex] && ! column.no_multifilter) {
				fields.push(column.dataIndex);
			}
		},this);
		
		this.Criteria = Ext.extend(Ext.ux.MultiFilter.Criteria,{
			gridColumns: columns,
			fieldList: fields
		});
	},
	
	updateFilterBtn: function() {
		var text = 'Filters';
		var iconCls = 'ra-icon-funnel'; //<-- no filters
		var count = this.filterCount();
		var fcount = this.filterCount(true);
		if(count) {
			text = 'Filters (' + count + ')';
			iconCls = 'ra-icon-funnel-edit'; //<-- only normal filters
			if(fcount) { iconCls = 'ra-icon-funnel-new-edit'; } //<-- both normal + frozen
		}
		else if(fcount) {
			iconCls = 'ra-icon-funnel-new'; //<-- only frozen filters
		}
		
		this.filtersBtn.setIconClass(iconCls);
		this.filtersBtn.setText(text);
	},
	
	filterCount: function(frozen,cust) {
		
		var filterdata = frozen ? this.store.filterdata_frozen : this.store.filterdata;
		filterdata = cust ? cust : filterdata;
		
		var recurseCount = function(item) {
			if(Ext.isObject(item)) {
				if (item['-and']) { return recurseCount(item['-and']); }
				if (item['-or']) { return recurseCount(item['-or']); }
				return 1;
			}
			if(Ext.isArray(item)) {
				var count = 0;
				Ext.each(item,function(i) {
					count = count + recurseCount(i);
				});
				return count;
			}
			return 0;
		}
	
		if (!filterdata) { return 0; }
		return recurseCount(filterdata);
	},
	
	showFilterWindow: function() {
		
		this.setFields();
		
		var plugin = this,frozen_header,freeze_btn,hlabel;
		
		var update_selections = function(set){
			var count = 0,fcount = 0;
			if(set) {
				set.filterdata_frozen = set.filterdata_frozen || [];
				fcount = plugin.filterCount(true,set.filterdata_frozen);
				count = set.items.length - 1;
			}
			
			hlabel.setText(get_header_html(fcount),false);
			frozen_header.setVisible(fcount);
			
			if(freeze_btn) { freeze_btn.setDisabled(!count); }
		};

		var get_header_html = function(size){
			return '<img src="/assets/rapidapp/misc/static/images/simple_new.png" style="padding-bottom:3px;">&nbsp;&nbsp;' +
				size + '&nbsp; Frozen (hidden) Filter Conditions Applied';
		};
		
		var hbuttons = [
			hlabel = new Ext.form.Label({
				itemId: 'heading',
				html: get_header_html(0),
				style: 'color:gray;font-size:1.2em;font-weight:bold;'
			})
		];
		
		var button_Align = 'right'; //<-- default
		var buttons = [];
		
		if(this.grid.allow_edit_frozen) {
			buttons.push( freeze_btn = new Ext.Button({
				text: 'Freeze Conditions',
				iconCls: 'ra-icon-arrow-up',
				handler: function(btn) {
					var win = btn.ownerCt.ownerCt,
						set = win.getComponent('filSet'),
						store = btn.ownerCt.ownerCt.multifilter.store;
					
					set.filterdata_frozen = set.filterdata_frozen || [];
					set.filterdata_frozen = set.filterdata_frozen.concat(set.getData());
					
					set.items.each(function(item){
						if(item.isFilterItem){ set.remove(item); }
					},this);
					
					update_selections(set);
					
				}
			}),'->');
			
			button_Align = 'left';

			hbuttons.push({
				xtype: 'button',
				style: 'padding-left:5px;',
				text: 'Un-Freeze Conditions',
				iconCls: 'ra-icon-arrow-down',
				handler: function(btn) {
					var win = btn.ownerCt.ownerCt.ownerCt,
						set = win.getComponent('filSet');
					
					var curdata = set.getData() || [];
					set.items.each(function(item){
						if(item.isFilterItem){ set.remove(item); }
					},this);
					
					set.loadData(set.filterdata_frozen.concat(curdata));
					set.filterdata_frozen = [];
					
					update_selections(set);
				}
			});
		}
		
		
		buttons.push({
			xtype: 'button',
			text: 'Save and Close',
			iconCls: 'ra-icon-save-ok',
			handler: function(btn) {
				var win = btn.ownerCt.ownerCt;
				var set = win.getComponent('filSet');
				var store = btn.ownerCt.ownerCt.multifilter.store;
				
				store.filterdata = set.getData();
				store.filterdata_frozen = set.filterdata_frozen;
				
				win.multifilter.updateFilterBtn();
				
				win.close();
        
        // Added for Github Issue #20 (and copied from Quick Search code)
        // clear start (necessary if we have paging) - resets to page 1
        if(store.lastOptions && store.lastOptions.params) {
          store.lastOptions.params[store.paramNames.start] = 0;
        }
        
				store.reload();
			}
		},
		
		{
			xtype: 'button',
			text: 'Cancel',
			//iconCls: 'ra-icon-close',
			handler: function(btn) {
				btn.ownerCt.ownerCt.close();
			}
		});
		
		frozen_header = new Ext.Panel({
			frame: true,
			anchor: '-0',
			style: 'padding:2px;',
			buttonAlign: 'center',
			buttons: hbuttons
		});
		
    // -- NEW: Set the window size taking the active 
    // browser size into account
    var winWidth = 750;
    var winHeight = 500;
    var browserSize = Ext.getBody().getViewSize();
    if (browserSize.width < winWidth) {
      winWidth = browserSize.width - 20;
    }
    if (browserSize.height < winHeight) {
      winHeight = browserSize.hight - 20;
    }
    if(winWidth < 300) { winWidth = 300 }
    if(winHeight < 150) { winHeight = 150 }
    // --
    
		var win = new Ext.Window({
		
			//id: 'mywin',
			multifilter: this,
			title: 'MultiFilter',
			layout: 'anchor',
			width: winWidth,
			height: winHeight,
			closable: true,
			modal: true,
			
			autoScroll: true,
			items: [
				frozen_header,
				new Ext.ux.MultiFilter.FilterSetPanel({
					FilterParams: {
						criteriaClass: this.Criteria
					},
					cls: 'x-toolbar x-small-editor',
					anchor: '-0', 
					frame: true,
					itemId: 'filSet'
				})
			],
			buttons: buttons,
			buttonAlign: button_Align
		});
		
		win.show();
		
		var set = win.getComponent('filSet');
		set.loadData(this.store.filterdata || []);
		set.filterdata_frozen = this.store.filterdata_frozen || [];
		set.on('remove',update_selections.createDelegate(this,[set]),this);
		set.on('add',update_selections.createDelegate(this,[set]),this,{ buffer: 20 });

		update_selections(set);
		
		return win;
	}
});


Ext.ux.MultiFilter.StaticCombo = Ext.extend(Ext.form.ComboBox,{
	mode: 'local',
	triggerAction: 'all',
	editable: false,
	value_list: false,
	valueField: 'valueField',
	displayField: 'displayField',
	initComponent: function() {
		if (this.value_list) {
			var data = [];
			Ext.each(this.value_list,function(item,index){
				data.push([index,item]);
			});
			//for (i in this.value_list) {
			//	data.push([i,this.value_list[i]]);
			//}
			//data.pop();
			this.store = new Ext.data.ArrayStore({
				fields: [
					this.valueField,
					this.displayField
				],
				data: data
			});
		}
		Ext.ux.MultiFilter.StaticCombo.superclass.initComponent.apply(this,arguments);
	}
});
Ext.reg('multifilter-sc', Ext.ux.MultiFilter.StaticCombo);


/*
Ext.ux.MultiFilter.defaultConditionMap = {

	'is'							: '=',
	'is equal to'				: '=',
	'equal to'					: '=',
	'is not equal to'			: '!=',
	'before'						: '<',
	'after'						: '>',
	'less than'				: '<',
	'greater than'			: '>',
	
	'contains'					: 'contains',
	'starts with'				: 'starts_with',
	'ends with'					: 'ends_with',
	"doesn't contain"			: 'not_contain',
	
	'null/empty status'  : 'null_empty',
	
	
	//'is null' : 'is_null',
	//'is empty' : 'is_empty',
	//'is null or empty': 'null_or_empty'

};
*/

// Moved condition remapping to the back end:
Ext.ux.MultiFilter.defaultConditionMap = {};

// This now needs to be simplified:
Ext.ux.MultiFilter.defaultTypeToConditionMap = {

	'default': {
		'is equal to'			: '=',
		'is not equal to'		: '!=',
		'contains'				: 'contains',
		"doesn't contain"		: 'not_contain',
		'starts with'			: 'starts_with',
		'ends with'				: 'ends_with',
		"doesn't start with"	: 'not_starts_with',
		"doesn't end with"	: 'not_ends_with',
		'less than'				: '<',
		'greater than'			: '>'
	},
	
  date: {
    'before'  : '<',
    'after'   : '>',
    'exactly' : '='
  },
	
  datetime: {
    'before'  : '<',
    'after'   : '>',
    'exactly' : '='
  },
	
	number: {
		'less than'				: '<',
		'greater than'			: '>',
		'equal to'				: '=',
		'not equal to'			: '!='
	},
	
	// bool is empty, leaving only 'is' in the dropdown, which
	// defaults to the editor, which in the case of bool should
	// allow the selection of the only possible values (0/1). No
	// other conditions make sense (>, <, etc)
	bool: {}

};


Ext.ux.MultiFilter.Criteria = Ext.extend(Ext.Container,{

	layout: 'hbox',
	
	autoEl: {},
	
	// Dummy default list of fields:
	fieldList: [ 'field1','field2','field3','field4' ],
	
	gridColumns: null,
	
	columnMap: {},
	
	fieldNameMap: {},
	reverseFieldNameMap: {},
	
	conditionMap: Ext.ux.MultiFilter.defaultConditionMap,
	
	typeCondMap: Ext.ux.MultiFilter.defaultTypeToConditionMap,
	
	// This is crap that I think shouldn't be required. 
	// Create an entire hidden version of the field_combo and add it
	// to the container just to get its Elelment so that Elelment
	// can be used to create an instance of TextMetrics just so that
	// we can accurately use it to measure text width:
	TM: function() {
		//var scope = this.constructor.prototype;
		var scope = this;
		if (!scope.TMinstance) {
			var cnf = {
				hidden: true,
				itemId: 'hidden_field_combo',
				name: 'hidden_field_combo'
			};
			Ext.applyIf(cnf,this.field_combo_cnf);
			
			scope.hiddenCombo = new Ext.ux.MultiFilter.StaticCombo(cnf);
			scope.add(scope.hiddenCombo);
			scope.doLayout();
			scope.TMinstance = Ext.util.TextMetrics.createInstance(scope.hiddenCombo.getEl());
		}
		return scope.TMinstance;
	},
	
	createFieldCombo: function() {
		var val_list = [];
		Ext.each(this.fieldList,function(item,index) {
			var val = item;
			if(this.reverseFieldNameMap[val]) {
				val = this.reverseFieldNameMap[val];
			}
			val_list.push(val);
		},this);
		Ext.apply(this.field_combo_cnf,{
			//value_list: this.fieldList
			value_list: val_list
		});
		
		this.field_combo_cnf.useMenuList = true;
		return Ext.ComponentMgr.create(this.field_combo_cnf,'static-combo');
		//return Ext.ComponentMgr.create(this.field_combo_cnf,'menu-field');
	},
	
	condType: 'default',
	
	createCondCombo: function() {
	
		var colCondCnf = this.typeCondMap[this.condType];
		if (!colCondCnf) { colCondCnf = this.typeCondMap['default']; }
		
		var value_list = [];
		// Ext.iterate instead of for(key in colCondCnf){...
		Ext.iterate(colCondCnf,function(key,value){
			value_list.push(key);
		});
		
		value_list.push('null/empty status');
		
		// Extra condition for use with rel_combo_field_cnf:
		value_list.push('is');
		
		Ext.apply(this.cond_combo_cnf,{
			value_list: value_list
		});
		//return new Ext.ux.MultiFilter.StaticCombo(this.cond_combo_cnf);
		return Ext.ComponentMgr.create(this.cond_combo_cnf,'static-combo');
	},
	
	createDataField: function () {
		
		var cnf = Ext.apply({},this.datafield_cnf);
		
		return Ext.ComponentMgr.create(cnf,'textfield');
	},
	
	getNullEmptyDfield: function () {
		
		var init_value = 'is null';// <-- default
		var value_list = [
			'is null', 
			'is empty', 
			'is null or empty',
			'is not null', 
			'is not empty', 
			'is not null or empty'
		];
		
		// Set to the current value ONLY if its one of the vals in the value_list
		var cur_value = this.datafield_cnf.value;
		Ext.each(value_list,function(val){
			if(cur_value == val) { init_value = cur_value; }
		},this);
		
		return {
			xtype: 'static-combo',
			value: init_value, 
			value_list: value_list
		};
	},
		
	initComponent: function() {
	
		this.reverseConditionMap = {};
		//for (i in this.conditionMap) {
		//	this.reverseConditionMap[this.conditionMap[i]] = i;
		//}
		Ext.iterate(this.conditionMap,function(key,val) {
			this.reverseConditionMap[val] = key;
		},this);
		
		this.initColumns();
		
		/* These are declared here instead of in the base class above because we 
		 * modify them later on, and we need to make sure they are attributes
		 * of the instance and not the class itself
		**/
		Ext.applyIf(this,{
			field_combo_cnf: {
				name: 'field_combo',
				itemId: 'field_combo',
				minListWidth: 200,
				width: 100,
				listeners: {
					// On select, set the value and call configSelector() to recreate the criteria container:
					select: function(combo) {
						var criteria = combo.ownerCt;
						var val = combo.getRawValue();
						Ext.apply(criteria.field_combo_cnf,{
							value: val
						});
						criteria.configSelector();
					}
				}
			},
			cond_combo_cnf: {
				name: 'cond_combo',
				itemId: 'cond_combo',
				width: 110,
				value_list: [],
				listeners: {
					select: function(combo) {
						var criteria = combo.ownerCt;
						var val = combo.getRawValue();
						
						if(val == criteria.last_cond_value) { return; }
						
						// clear the data field if we're switching from null/empty:
						if(criteria.last_cond_value == 'null/empty status') {
							delete criteria.datafield_cnf.value;
						}
						
						//if(val != 'is' && criteria.last_cond_value != 'is') { return; }
						
						Ext.apply(criteria.cond_combo_cnf,{
							value: val
						});
						
						// Set criteria.last_cond_value (used above and also in configSelector login below)
						Ext.apply(criteria,{
							last_cond_value: val
						});

						criteria.configSelector();
					}
				}
			},
			datafield_cnf: {
				xtype	: 'textfield',
				name	: 'datafield',
				itemId: 'datafield',
				flex	: 1
			}
		});
	
		this.items = this.createFieldCombo();

		Ext.ux.MultiFilter.Criteria.superclass.initComponent.call(this);
	},
	
	initColumns: function() {
		if (! this.gridColumns) { return; }
		
		this.columnMap = {};
		//for (var i = 0; i < this.gridColumns.length; i++) {
		//	var column = this.gridColumns[i];
		//	this.columnMap[column.name] = column;
		//}
		Ext.each(this.gridColumns,function(item,index) {
			var column = item;
			this.columnMap[column.name] = column;
			if (column.header) {
				this.fieldNameMap[column.header] = column.name;
			}
		},this);
		
		this.reverseFieldNameMap = {};
		Ext.iterate(this.fieldNameMap,function(key,val) {
			this.reverseFieldNameMap[val] = key;
		},this);
		
		return this.columnMap;
	},
	
	configSelector: function() {
		
		// reset condType to default:
		this.condType = 'default';
		
		var cust_dfield_cnf = null;
		
		if (this.field_combo_cnf.value) {
			var TM = this.TM();
			var width = 30 + TM.getWidth(this.field_combo_cnf.value);
			Ext.apply(this.field_combo_cnf,{
				width: width
			});
			
			var fval = this.field_combo_cnf.value;
			if(this.fieldNameMap[fval]) {
				fval = this.fieldNameMap[fval];
			}
			var column = this.columnMap[fval];
			// Get the type from the filter.type property of the column model:
			if (column && column.filter && column.filter.type) {
				this.condType = column.filter.type;
			}
			
			// new: column.filter.type above is no longer set since TableSpec stuff:
			if (column && column.multifilter_type) {
				this.condType = column.multifilter_type;
			}

			if (column && column.rel_combo_field_cnf && this.last_cond_value == 'is') {
				cust_dfield_cnf = {};
				Ext.apply(cust_dfield_cnf,column.rel_combo_field_cnf);
				delete cust_dfield_cnf.id;
				delete cust_dfield_cnf.width;
			}
			
			if(this.last_cond_value == 'null/empty status') {
				cust_dfield_cnf = this.getNullEmptyDfield();
			}
			
		}
				
		if(this.datafield_cnf.width) { delete this.datafield_cnf.width; }

		if(cust_dfield_cnf) {
			// Make sure we preserve the existing value of 'value'
			// (this is an issue with 'menu-field' setup by RapidApp::Column)
			if(typeof this.datafield_cnf.value != 'undefined') {
				cust_dfield_cnf.value = this.datafield_cnf.value;
			}
			
			Ext.apply(this.datafield_cnf,cust_dfield_cnf);
			
			// Make sure itemId is 'datafield'
			// TODO: find a new way to do the lookup. If the cust_dfield/editor had
			// an itemId it might have needed it for something that could be broken by this
			this.datafield_cnf.itemId = 'datafield';
		}
		else if (this.condType == 'date') {
			Ext.apply(this.datafield_cnf,{
				xtype	: 'datefield',
				plugins: ['form-relative-datetime'],
				noReplaceDurations: true, //<-- option of the form-relative-datetime plugin
				format: 'Y-m-d'
			});
		}
		else if (this.condType == 'datetime') {
			Ext.apply(this.datafield_cnf,{
				xtype	: 'datefield',
				plugins: ['form-relative-datetime'],
				noReplaceDurations: true, //<-- option of the form-relative-datetime plugin
				format: 'Y-m-d H:i'
			});
		}
		else if(this.condType == 'number') {
			Ext.apply(this.datafield_cnf,{
				xtype	: 'numberfield',
				style: 'text-align:left;',
				flex: 1
			});
		}
		else {
			Ext.apply(this.datafield_cnf,{
				xtype	: 'textfield',
				itemId: 'datafield',
				flex	: 1
			});
		}

		// Remove all the fields and add all back in from scratch to
		// get hbox to set the correct sizes:
		this.removeAll(true);
		this.add(
			this.createFieldCombo(),
			this.createCondCombo(),
			this.createDataField()
		);
		this.doLayout();
	},
	
	getData: function() {
		var field_combo = this.getComponent('field_combo'),
			cond_combo = this.getComponent('cond_combo'),
			datafield = this.getComponent('datafield');
		
		var field = field_combo ? field_combo.getRawValue() : null,
			cond = cond_combo ? cond_combo.getRawValue() : null,
			val = null;
		
		if(datafield) {
			val = datafield.xtype == 'datefield' ? 
				// Special case for datefield ONLY: use getRawValue to optionally preserve
				// the relative date string which has special handling to convert on the 
				// server side
				datafield.getRawValue() : 
				
				// ALL other kinds of fields should use the normal getValue function:
				datafield.getValue();
		}
		
		
		if(!field || !cond) { return null; }
		
		//field combo
		if(field && this.fieldNameMap[field]) {
			field = this.fieldNameMap[field];
		}
		
		/* Moved into the back end:
		// --- translate relationship column to its id *or* render col ---
		var column = this.columnMap[field];
		if(column) {
			
			if (cond == 'is') {
				if(column.query_id_use_column) { field = column.query_id_use_column; }
			}
			else {
				if(column.query_search_use_column) { field = column.query_search_use_column; }
			}
		}
		// --- ---
		
		*/

		
		if(cond && this.conditionMap[cond]) {
			cond = this.conditionMap[cond];
		}
		
		var data = {};
		data[field] = {};
		data[field][cond] = val;
			
		return data;
	},
	
	loadData: function(data) {

		Ext.iterate(data,function(k,v) {
			
			//field combo
			if(this.reverseFieldNameMap[k]) {
				k = this.reverseFieldNameMap[k];
			}
			
			this.field_combo_cnf.value = k;
			Ext.iterate(v,function(k2,v2) {
				var cond = k2;
				if(this.reverseConditionMap[cond]) {
					cond = this.reverseConditionMap[cond];
				}
				this.cond_combo_cnf.value = cond;
				this.datafield_cnf.value = v2;

				this.last_cond_value = cond;
			},this);
		},this);
		
		this.configSelector();
	}
});



Ext.ux.MultiFilter.Filter = Ext.extend(Ext.Container,{

	layout: 'hbox',
	
	isFilterItem: true,
	
	cls: 'x-toolbar x-small-editor', // < --- this makes the container look like a toolbar
	//cls: 'x-toolbar', // < --- this makes the container look like a toolbar
	style: {
		margin: '5px 5px 5px 5px',
		'border-width': '1px 1px 1px 1px'
	},
	
	//height: 40,
	
	defaults: {
		flex: 1
	},
	
	autoScroll: true,
	
	criteriaClass: Ext.ux.MultiFilter.Criteria,
	
	initComponent: function() {

		if (! this.filterSelection) {
			this.filterSelection = new this.criteriaClass({
				flex: 1
			});
		}

		this.items = [
		
			new Ext.ux.MultiFilter.StaticCombo({
				name: 'and_or',
				itemId: 'and_or',
				width: 30,
				hideTrigger: true,
				value: 'and',
				value_list: [
					'and',
					'or'
				]
			}),
		
			/* TODO: Get "not" button implemented and working 
			{
				xtype: 'button',
				text: '!',
				enableToggle: true,
				flex: 0,
				tooltip: 'Toggle invert ("not")',
				toggleHandler: function(btn,state) {
					if (state) {
						btn.btnEl.replaceClass('x-multifilter-not-off', 'x-multifilter-not-on');
					}
					else {
						btn.btnEl.replaceClass('x-multifilter-not-on', 'x-multifilter-not-off');
					}
				},
				listeners: {
					'render': function(btn) {
						btn.btnEl.addClass('x-multifilter-not');
					}
				}
			},
			*/
	
			this.filterSelection,
			
			{
				//xtype: 'button',
				//iconCls: 'ra-icon-arrow-down',
				xtype: 'boxtoolbtn',
				toolType: 'down',
				flex: 0,
				itemId: 'down-button',
				handler: function(btn) {
					//var filter = btn.ownerCt;
					//var set = btn.ownerCt.ownerCt;
					var filter = this;
					var set = this.ownerCt;
					return Ext.ux.MultiFilter.movefilter(set,filter,1);
				},
				scope: this
			},
			
			{
				//xtype: 'button',
				//iconCls: 'ra-icon-arrow-up',
				xtype: 'boxtoolbtn',
				toolType: 'up',
				flex: 0,
				itemId: 'up-button',
				handler: function(btn) {
					//var filter = btn.ownerCt;
					//var set = btn.ownerCt.ownerCt;
					var filter = this;
					var set = this.ownerCt;
					return Ext.ux.MultiFilter.movefilter(set,filter,-1);
				},
				scope: this
			},
			
			{
				//xtype: 'button',
				//iconCls: 'ra-icon-delete',
				xtype: 'boxtoolbtn',
				toolType: 'close',
				flex: 0,
				handler: function(btn) {
					//var filter = btn.ownerCt;
					//var set = btn.ownerCt.ownerCt;
					var filter = this;
					var set = this.ownerCt;
					set.remove(filter,true);
					set.bubble(function(){ this.doLayout(); });
				},
				scope: this
			},
			{ xtype: 'spacer', width: 2 }
		];

		Ext.ux.MultiFilter.Filter.superclass.initComponent.apply(this,arguments);
	},

	checkPosition: function() {
	
		var set = this.ownerCt;
		var index = set.items.indexOfKey(this.getId());
		var max = set.items.getCount() - 2;
		
		var upBtn = this.getComponent('up-button');
		var downBtn = this.getComponent('down-button');
		var and_or = this.getComponent('and_or');
		
		if(index == 0) {
			upBtn.setVisible(false);
			and_or.setValue('and');
			and_or.setVisible(false);
		}
		else {
			upBtn.setVisible(true);
			and_or.setVisible(true);
		}
		
		if(index >= max) {
			downBtn.setVisible(false);
		}
		else {
			downBtn.setVisible(true);
		}
	},
	
	isOr: function() {
		var and_or = this.getComponent('and_or');
		if (and_or.getRawValue() == 'or') {
			return true;
		}
		return false;
	},
	
	setOr: function(bool) {
		var and_or = this.getComponent('and_or');
		if(bool) {
			and_or.setRawValue('or');
		}
		else {
			and_or.setRawValue('and');
		}
	},
	
	getData: function() {
		var data = this.filterSelection.getData();
		return data;
	},
	
	loadData: function(data) {
		return this.filterSelection.loadData(data);
	}
});
Ext.reg('filteritem',Ext.ux.MultiFilter.Filter);



Ext.ux.MultiFilter.FilterSetPanel = Ext.extend(Ext.Panel,{

	autoHeight: true,
	
	FilterParams: {},

	initComponent: function() {
		
		var add_filter = {
			xtype: 'button',
			text: 'Add',
			iconCls: 'ra-icon-add',
			//handler: function(btn) {
			//	btn.ownerCt.ownerCt.addFilter();
			//},
			handler: this.addFilter,
			scope: this
		};
		
		var add_set = {
			xtype: 'button',
			text: 'Add Set',
			iconCls: 'ra-icon-add',
			//handler: function(btn) {
			//	btn.ownerCt.ownerCt.addFilterSet();
			//},
			handler: this.addFilterSet,
			scope: this
		};
		
		this.items = {
			xtype: 'container',
			layout: 'hbox',
			style: {
				margin: '2px 2px 2px 2px'
			},
			items: [
				add_filter,
				add_set		
			]
		};
	
		var checkPositions = function(set) {
			set.items.each(function(item,indx,length) {
				if(item.getXType() !== 'filteritem') { return; }
				item.checkPosition();
			},this);
		};
		
		this.on('add',checkPositions,this);
		this.on('remove',checkPositions,this);
	
		Ext.ux.MultiFilter.FilterSetPanel.superclass.initComponent.call(this);
	},
	
	addNewItem: function(item) {
		var count = this.items.getCount();
		this.insert(count - 1,item);
		this.bubble(function(){ this.doLayout(); });
		return item;
	},
	
	addFilter: function(data) {
		return this.addNewItem(new Ext.ux.MultiFilter.Filter(this.FilterParams));
	},
	
	addFilterSet: function(data) {
	
		var config = {
			filterSelection: new Ext.ux.MultiFilter.FilterSetPanel({ FilterParams: this.FilterParams })
		};
	
		return this.addNewItem(new Ext.ux.MultiFilter.Filter(config));
	},
	
	addFilterWithData: function(item) {

		var filter;
		var new_item = item;
		
		// prune filters out of sets with only 1 filter:
		if(Ext.isArray(item) && item.length == 1 && ! item[0]['-or']) {
			new_item = item[0];
		}
		if(Ext.isArray(new_item) && new_item.length == 1  && ! new_item[0]['-or']) {
			new_item = new_item[0];
		}
		
		if(item['-and']) {
			new_item = item['-and'];
		}
		if(item['-or']) {
			new_item = item['-or'];
			for (var j = 0; j < new_item.length; j++) {
				filter = this.addFilterWithData(new_item[j]);
				filter.setOr(true);
			}
			return;
		}
		
		if(Ext.isObject(new_item)) {
			filter = this.addFilter();
		}
		else if(Ext.isArray(new_item)) {
		
			// Skip empty filtersets:
			if(new_item.length == 0) {
				return;
			}
		
			filter = this.addFilterSet();
			if(new_item.length == 1) {
				var only_item = new_item[0];
				if(Ext.isArray(only_item)) {
					new_item = only_item;
				}
			}
		}

		filter.loadData(new_item);

		return filter;
	},

	getData: function() {
	
		var data = [];
		var curdata = data;
		var or_sequence = false;
		
		this.items.each(function(item,indx,length) {
			if(item.getXType() !== 'filteritem') { return; }
			
			var itemdata = item.getData.call(item);
			if(!itemdata) { return; }
			
			if (item.isOr()) {
				or_sequence = true;
				var list = data.slice();
				data = [];
				curdata = [];
				curdata.push(list);
				data.push({'-or': curdata});
			}
			else {
				if (or_sequence) {
					var list = [];
					var last = curdata.pop();
					list.push(last);
					curdata.push({'-and': list});
					curdata = list;
				}
				or_sequence = false;
			}
			
			curdata.push(itemdata);
			
		},this);
		
		return data;
	},
	
	loadData: function(data,setOr) {
		return Ext.each(data,function(item) {
			this.addFilterWithData(item);
		},this);
	}
});
Ext.reg('filtersetpanel',Ext.ux.MultiFilter.FilterSetPanel);



Ext.ux.MultiFilter.movefilter = function(set,filter,indexOffset) {

	var filter_id = filter.getId();
	var index = set.items.indexOfKey(filter_id);
	var newIndex = index + indexOffset;
	var max = set.items.getCount() - 1;
	
	if (newIndex < 0 || newIndex >= max || newIndex == index) return;
	
	set.remove(filter,false);
	var d = filter.getPositionEl().dom;
	d.parentNode.removeChild(d);
	set.insert(newIndex,filter);
	
	set.bubble(function(){ this.doLayout(); });
}


// --------
// http://www.sencha.com/forum/showthread.php?33475-Tip-Long-menu-overflow/page2
Ext.override(Ext.menu.Menu, {
    // See http://extjs.com/forum/showthread.php?t=33475&page=2
    showAt : function(xy, parentMenu, /* private: */_e) {
        this.parentMenu = parentMenu;
        if (!this.el) {
            this.render();
        }
        if (_e !== false) {
            this.fireEvent("beforeshow", this);
            xy = this.el.adjustForConstraints(xy);
        }
        this.el.setXY(xy);

        // Start of extra logic to what is in Ext source code...
        // See http://www.extjs.com/deploy/ext/docs/output/Menu.jss.html
        // get max height from body height minus y cordinate from this.el
        var maxHeight = this.maxHeight || Ext.getBody().getHeight() - xy[1];
        if (this.el.getHeight() > maxHeight) {
            // set element with max height and apply vertical scrollbar
            this.el.setHeight(maxHeight);
            this.el.applyStyles('overflow-y: auto;');
        }
        // .. end of extra logic to what is in Ext source code

        this.el.show();
        this.hidden = false;
        this.focus();
        this.fireEvent("show", this);
    },
	 
	// Added 2012-04-02 by HV: further turn off the default tiny menu scroller functions:
	enableScrolling: false
	 
});
// --------


// Mouse-over/hover fix:
// http://www.sencha.com/forum/showthread.php?69090-Ext.ux.form.SuperBoxSelect-as-seen-on-facebook-and-hotmail&p=515731#post515731
Ext.override(Ext.ux.form.SuperBoxSelectItem, {
	enableElListeners : function() {
		this.el.on('click', this.onElClick, this, {stopEvent:true});
		//this.el.addClassOnOver('x-superboxselect-item x-superboxselect-item-hover');
		this.el.addClassOnOver('x-superboxselect-item-hover');
	}
});


// Override to get rid of the input cursor if editable is false
Ext.override(Ext.ux.form.SuperBoxSelect,{
	initComponent_orig: Ext.ux.form.SuperBoxSelect.prototype.initComponent,
	initComponent: function () {
		this.initComponent_orig.apply(this,arguments);
		this.on('afterrender',function(combo) {
			if(combo.editable === false && combo.hideInput === true) {
				combo.inputEl.removeClass("x-superboxselect-input");
				combo.inputEl.setVisible(false);
			}
		});
	}
});


/**
 * We override Connection so that **Every** AJAX request gets our processing added to it.
 * We first set up a closure that can re-issue the current request, and then check headers
 *   to see if we want to interrupt the current one.
 */
Ext.override(Ext.data.Connection,{
	//request_orig: Ext.data.Connection.prototype.request,
	//request: function(opts) {
	//	return this.request_orig(opts);
	//},
	
	handleResponse_orig: Ext.data.Connection.prototype.handleResponse,
	handleResponse : function(response){
		this.fireEvent('requestcomplete',this,response,response.argument.options);
		
		var options = response.argument.options;
		
		var thisConn = this;
		var success_callback_repeat = function(newopts) {
			// Optional changes/additions to the original request options:
			if(Ext.isObject(newopts)) {
				Ext.iterate(newopts,function(key,value){
					Ext.apply(options[key],value);
				});
			}
			thisConn.request(options);
		};
		
		var orig_args= arguments;
		var current_callback_continue= function() { thisConn.handleResponse_orig.apply(thisConn,orig_args); };
		
		Ext.ux.RapidApp.handleCustomServerDirectives(response, current_callback_continue, success_callback_repeat);
	},
	
	doFormUpload_orig: Ext.data.Connection.prototype.doFormUpload,
	doFormUpload : function(o, ps, url){
		var thisConn= this;
		var success_callback_repeat = function(newopts) {
			// Optional changes/additions to the original request options:
			if(Ext.isObject(newopts)) {
				Ext.iterate(newopts,function(key,value){
					Ext.apply(o[key],value);
				});
			}
			thisConn.doFormUpload(o, ps, url);
		};
		
		// had to copy/paste from Ext.data.Connection, since there were no smaller routines to subclass...
		var id = Ext.id(),
			doc = document,
			frame = doc.createElement('iframe'),
			form = Ext.getDom(o.form),
			hiddens = [],
			hd,
			encoding = 'multipart/form-data',
			buf = {
				target: form.target,
				method: form.method,
				encoding: form.encoding,
				enctype: form.enctype,
				action: form.action
			};
		
		Ext.fly(frame).set({
			id: id,
			name: id,
			cls: 'x-hidden',
			src: Ext.SSL_SECURE_URL
		}); 
		
		doc.body.appendChild(frame);
		
		if(Ext.isIE){
			document.frames[id].name = id;
		}
		
		Ext.fly(form).set({
			target: id,
			method: 'POST',
			enctype: encoding,
			encoding: encoding,
			action: url || buf.action
		});
		
		var addParam= function(k, v){
			hd = doc.createElement('input');
			Ext.fly(hd).set({
				type: 'hidden',
				value: v,
				name: k
			});
			form.appendChild(hd);
			hiddens.push(hd);
		};
		
		addParam('RequestContentType', 'text/x-rapidapp-form-response');
		Ext.iterate(Ext.urlDecode(ps, false), addParam);
		if (o.params)
			Ext.iterate(o.params, addParam);
		if (o.headers)
			Ext.iterate(o.headers, addParam);
		
		function cb(){
			var me = this,
				r = {responseText : '',
					responseXML : null,
					responseHeaders : {},
					getResponseHeader: function(key) { return this.responseHeaders[key]; },
					argument : o.argument},
				doc,
				firstChild;
			
			try{
				doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
				if(doc){
					// Here, we modify the ExtJS stuff to also include out-of-band data that would normally be
					//   in the headers.  We store it in a second textarea
					var header_json_textarea= doc.getElementById('header_json');
					var json_textarea= doc.getElementById('json');
					
					if (header_json_textarea) {
						r.responseHeaderJson= header_json_textarea.value;
						r.responseHeaders= Ext.decode(r.responseHeaderJson) || {};
					}
					
					if (json_textarea) {
						r.responseText= json_textarea.value;
					}
					else if (doc.body) {
						if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ 
							r.responseText = firstChild.value;
						}else{
							r.responseText = doc.body.innerHTML;
						}
					}
					
					r.responseXML = doc.XMLDocument || doc;
				}
			}
			catch(e) {}
			
			Ext.EventManager.removeListener(frame, 'load', cb, me);
			
			me.fireEvent('requestcomplete', me, r, o);
			
			function current_callback_continue(fn, scope, args){
				if(Ext.isFunction(o.success)) o.success.apply(o.scope, [r, o]);
				if(Ext.isFunction(o.callback)) o.callback.apply(o.scope, [o, true, r]);
			}
			
			Ext.ux.RapidApp.handleCustomServerDirectives(r, current_callback_continue, success_callback_repeat);
			
			if(!me.debugUploads){
				setTimeout(function(){Ext.removeNode(frame);}, 100);
			}
		}
		
		Ext.EventManager.on(frame, 'load', cb, this);
		form.submit();
		
		Ext.fly(form).set(buf);
		Ext.each(hiddens, function(h) {
			Ext.removeNode(h);
		});
	}
});





Ext.override(Ext.BoxComponent, {
	initComponent: function() {


		// All-purpose override allowing eval code in config
		var thisB = this;
		if (thisB.afterRender_eval) { this.on('afterrender', function() { eval(thisB.afterRender_eval); }) }
		var config = this;
		if (this.init_evalOverrides) {
			for ( var i in this.init_evalOverrides ) {
				config[i] = eval(this.init_evalOverrides[i]);
			}
			Ext.apply(this, Ext.apply(this.initialConfig, config));
		}
		Ext.BoxComponent.superclass.initComponent.apply(this, arguments);
	}
	//,afterRender: function() {
		//this.superclass.afterRender.call(this);
	//	if (this.afterRender_eval) { eval(this.afterRender_eval); }

	//}
});



Ext.override(Ext.Container, {
	onRender: function() {
		Ext.Container.superclass.onRender.apply(this, arguments);
		
		if (this.onRender_eval) { eval(this.onRender_eval); }

		var thisC = this;

		if (this.ajaxitems && Ext.isArray(this.ajaxitems)) {

			for (i in this.ajaxitems) {
				if (this.ajaxitems[i]['url']) {

					alert(this.ajaxitems[i]['url']);

					Ext.Ajax.request({
						disableCaching: true,
						url: this.ajaxitems[i]['url'],
						params: this.ajaxitems[i]['params'],
						success: function(response, opts) {
							var imported_data = eval('(' + response.responseText + ')');
							thisC.add(new Ext.Container(imported_data));
							thisC.doLayout();
						},
						failure: function(response, opts) {
							alert('AJAX ajaxitems FAILED!!!!!!');
						}
					});
				}
			}
		}
	}
});




Ext.override(Ext.ux.grid.GridFilters, {

	initOrig: Ext.ux.grid.GridFilters.prototype.init,

	init: function(grid) {
		this.initOrig.apply(this, arguments);

		if (this.init_state) {

			for (i in this.init_state.filters) {
				for (p in this.init_state.filters[i]) {
					var orig = this.init_state.filters[i][p];
					if (p == 'before' || p == 'after' || p == 'on') {
						this.init_state.filters[i][p] = Date.parseDate(orig,"Y-m-d\\TH:i:s");
					}
				}
			}

			this.applyState(grid,this.init_state);
			grid.applyState(this.init_state);
			//console.dir(this.init_state);
		}
	},

	getState: function () {
		var filters = {};
		this.filters.each(function (filter) {
			if (filter.active) {
				filters[filter.dataIndex] = filter.getValue();
			}
		});
		return filters;
	}
});



// Tweaks to Saki's "CheckTree" (http://checktree.extjs.eu/) -- 2010-03-27 by HV
Ext.override(Ext.ux.tree.CheckTreePanel, {
	// This is required in order to get initial checked state:
	afterRender:function() {
		Ext.ux.tree.CheckTreePanel.superclass.afterRender.apply(this, arguments);
		this.updateHidden();
	 },

	 // This adds unchecked items to the posted list... Unchecked start with '-', checked start with '+'
	 getValue:function() {
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push('+' + n.id);
				}
			}
			else {
				a.push('-' + n.id);
			}
		}, this);
		a.shift(); // Remove root element
		return a;
	}
});


/* Override to force it to not display the checkbox if "checkbox" is null */
Ext.override(Ext.ux.tree.CheckTreeNodeUI, {

	renderElements:function(n, a, targetNode, bulkRender){

		/* This override was required to support NO checkbox */
		var checkbox_class = 'x-tree-checkbox';
		if (n.attributes.checked == null) { checkbox_class = 'x-tree-checkbox-no-checkbox'; }
		/* ------------------------------------------------- */

		this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() :'';
		var checked = n.attributes.checked;
		var href = a.href ? a.href : Ext.isGecko ? "" :"#";
		  var buf = [
			 '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">'
			,'<span class="x-tree-node-indent">',this.indentMarkup,"</span>"
			,'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />'
			,'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" :""),(a.iconCls ? " "+a.iconCls :""),'" unselectable="on" />'
			,'<img src="'+this.emptyIcon+'" class="' + checkbox_class +(true === checked ? ' x-tree-node-checked' :'')+'" />'
			,'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" '
			,a.hrefTarget ? ' target="'+a.hrefTarget+'"' :"", '><span unselectable="on">',n.text,"</span></a></div>"
			,'<ul class="x-tree-node-ct" style="display:none;"></ul>'
			,"</li>"
		].join('');
		var nel;
		if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
			this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
		}else{
			this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
		}
		this.elNode = this.wrap.childNodes[0];
		this.ctNode = this.wrap.childNodes[1];
		var cs = this.elNode.childNodes;
		this.indentNode = cs[0];
		this.ecNode = cs[1];
		this.iconNode = cs[2];
		this.checkbox = cs[3];
		this.cbEl = Ext.get(this.checkbox);
		this.anchor = cs[4];
		this.textNode = cs[4].firstChild;
	} // eo function renderElements
});




/*
Ext.override(Ext.chart.LineChart, {
	initComponent: function() {
		var config = this;
		if (this.xAxis && this.xAxis['xtype']) {
			if(this.xAxis['xtype'] == 'categoryaxis') { config['xAxis'] = new Ext.chart.CategoryAxis(this.xAxis); }
			if(this.xAxis['xtype'] == 'numericaxis') { config['xAxis'] = new Ext.chart.NumericAxis(this.xAxis); }
		}
		if (this.yAxis && this.yAxis['xtype']) {
			if(this.yAxis['xtype'] == 'categoryaxis') { config['yAxis'] = new Ext.chart.CategoryAxis(this.yAxis); }
			if(this.yAxis['xtype'] == 'numericaxis') { config['yAxis'] = new Ext.chart.NumericAxis(this.yAxis); }
		}
		Ext.apply(this, Ext.apply(this.initialConfig, config));
		Ext.chart.LineChart.superclass.initComponent.apply(this, arguments);
	}
});
*/


var pxMatch = /(\d+(?:\.\d+)?)px/;
Ext.override(Ext.Element, {
		  getViewSize : function(contentBox){
				var doc = document,
					 me = this,
					 d = me.dom,
					 extdom = Ext.lib.Dom,
					 isDoc = (d == doc || d == doc.body),
					 isBB, w, h, tbBorder = 0, lrBorder = 0,
					 tbPadding = 0, lrPadding = 0;
				if (isDoc) {
					 return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
				}
				isBB = me.isBorderBox();
				tbBorder = me.getBorderWidth('tb');
				lrBorder = me.getBorderWidth('lr');
				tbPadding = me.getPadding('tb');
				lrPadding = me.getPadding('lr');

				// Width calcs
				// Try the style first, then clientWidth, then offsetWidth
				if (w = me.getStyle('width').match(pxMatch)){
					 if ((w = Math.round(w[1])) && isBB){
						  // Style includes the padding and border if isBB
						  w -= (lrBorder + lrPadding);
					 }
					 if (!contentBox){
						  w += lrPadding;
					 }
					 // Minimize with clientWidth if present
					 d.clientWidth && (d.clientWidth < w) && (w = d.clientWidth);
				} else {
					 if (!(w = d.clientWidth) && (w = d.offsetWidth)){
						  w -= lrBorder;
					 }
					 if (w && contentBox){
						  w -= lrPadding;
					 }
				}

				// Height calcs
				// Try the style first, then clientHeight, then offsetHeight
				if (h = me.getStyle('height').match(pxMatch)){
					 if ((h = Math.round(h[1])) && isBB){
						  // Style includes the padding and border if isBB
						  h -= (tbBorder + tbPadding);
					 }
					 if (!contentBox){
						  h += tbPadding;
					 }
					 // Minimize with clientHeight if present
					 d.clientHeight && (d.clientHeight < h) && (h = d.clientHeight);
				} else {
					 if (!(h = d.clientHeight) && (h = d.offsetHeight)){
						  h -= tbBorder;
					 }
					 if (h && contentBox){
						  h -= tbPadding;
					 }
				}

				return {
					 width : w,
					 height : h
				};
		  }
});

Ext.override(Ext.layout.ColumnLayout, {
	 onLayout : function(ct, target, targetSize){
		  var cs = ct.items.items, len = cs.length, c, i;

		  if(!this.innerCt){
				// the innerCt prevents wrapping and shuffling while
				// the container is resizing
				this.innerCt = target.createChild({cls:'x-column-inner'});
				this.innerCt.createChild({cls:'x-clear'});
		  }
		  this.renderAll(ct, this.innerCt);

		  var size = targetSize || target.getViewSize(true);

		  if(size.width < 1 && size.height < 1){ // display none?
				return;
		  }

		  var w = size.width - this.scrollOffset,
				h = size.height,
				pw = w;

		  this.innerCt.setWidth(w);

		  // some columns can be percentages while others are fixed
		  // so we need to make 2 passes

		  for(i = 0; i < len; i++){
				c = cs[i];
				if(!c.columnWidth){
					 pw -= (c.getSize().width + c.getPositionEl().getMargins('lr'));
				}
		  }

		  pw = pw < 0 ? 0 : pw;

		  for(i = 0; i < len; i++){
				c = cs[i];
				if(c.columnWidth){
					 c.setSize(Math.floor(c.columnWidth * pw) - c.getPositionEl().getMargins('lr'));
				}
		  }
		  // Do a second pass if the layout resulted in a vertical scrollbar (changing the available width)
		  if (!targetSize && ((size = target.getViewSize(true)).width != w)) {
				this.onLayout(ct, target, size);
		  }
	 }
});

/* http://www.sencha.com/forum/showthread.php?95486-Cursor-Position-in-TextField&p=609639&viewfull=1#post609639 */
Ext.override(Ext.form.Field, {

    setCursorPosition: function(pos) {
       var el = this.getEl().dom;
		 if(!el) { return; } // <-- rare cases this is undef and throws error
       if (el.createTextRange) {
          var range = el.createTextRange();
          range.move("character", pos);
          range.select();
       } else if(typeof el.selectionStart == "number" ) { 
          el.focus(); 
          el.setSelectionRange(pos, pos); 
       } else {
         //alert('Method not supported');
         return;
       }
    },

    getCursorPosition: function() {
       var el = this.getEl().dom;
       var rng, ii=-1;
       if (typeof el.selectionStart=="number") {
          ii=el.selectionStart;
       } else if (document.selection && el.createTextRange){
          rng=document.selection.createRange();
          rng.collapse(true);
          rng.moveStart("character", -el.value.length);
          ii=rng.text.length;
       }
       return ii;
    }
   
});


Ext.Updater.defaults.disableCaching = true;

Ext.ns('Ext.log');
Ext.log = function() {};

Ext.ns('Ext.ux.RapidApp');

// This should be set dynamically by the server:
Ext.ux.RapidApp.VERSION = Ext.ux.RapidApp.VERSION || 0;

// Window Group for Custom Prompts to make them higher than other windows and load masks
Ext.ux.RapidApp.CustomPromptWindowGroup = new Ext.WindowGroup();
Ext.ux.RapidApp.CustomPromptWindowGroup.zseed = 20050;
	
/* Global Server Event Object */
Ext.ux.RapidApp.EventObjectClass = Ext.extend(Ext.util.Observable,{
	constructor: function(config) {
		this.addEvents('serverevent');
		Ext.ux.RapidApp.EventObjectClass.superclass.constructor.call(this,config);
		this.on('serverevent',this.onServerEvent,this);
	},
	
	Fire: function() {
		var a = arguments;
		var arg_list = [ "'serverevent'" ];
		for( i = 0; i < a.length; i++) {
			arg_list.push('a[' + i + ']');
		}
		var eval_str = 'this.fireEvent(' + arg_list.join(',') + ');';
		eval( eval_str );
	},
	
	handlerMap: {},
	
	attachServerEvents: function() {
		var a = Array.prototype.slice.call(arguments, 0);
		var handler = a.shift();
		Ext.each(a,function(event) {
			this.attachHandlerToEvent(handler,event);
		},this);
	},
	
	attachHandlerToEvent: function(handler,event) {
		if(! Ext.isObject(handler) || ! Ext.isFunction(handler.func) || ! Ext.isString(handler.id)) {
			throw "handler must be an object with func and id";
		}
		
		if (! Ext.isArray(this.handlerMap[event])) { this.handlerMap[event] = []; }

		var skip = false;
		Ext.each(this.handlerMap[event],function(item) {
			// Skip adding if its already in the list:
			if (handler.id == item.id) {
				skip = true;
			};
		});
		
		if(skip) { return; }
		
		return this.handlerMap[event].push(handler);
	},
	
	onServerEvent: function() {
		var events = Array.prototype.slice.call(arguments, 0);
		var handlers = [];
		var seenIds = {};
		
		Ext.each(events,function(event) {
			if(Ext.isArray(this.handlerMap[event])) {
				Ext.each(this.handlerMap[event],function(handler) {
					if(!seenIds[handler.id]++) {
						handlers.push(handler);
					}
				},this);
			}
		},this);
		
		return this.callHandlers(handlers);
	},
	
	callHandlers: function(handlers) {
		Ext.each(handlers,function(handler) {
			var scope = Ext.getCmp(handler.id);
			if (scope) {
				handler.func.call(scope);
			}
			else {
				// TODO: remove the invalid id from handlerMap
				
			}
		},this);
	}
});
Ext.ux.RapidApp.EventObject = new Ext.ux.RapidApp.EventObjectClass();
	

Ext.ns('Ext.ux.RapidApp.userPrefs');
Ext.ux.RapidApp.userPrefs.timezone= 'America/New_York';
Ext.ux.RapidApp.userPrefs.timezoneOffset= -5*60;
Ext.ux.RapidApp.userPrefs.dateFormat= 'Y M j, g:i a';
Ext.ux.RapidApp.userPrefs.nearDateFormat= 'D M j, g:i a';

Ext.ns('Ext.ux.form.FormConnectorField');
Ext.ux.form.FormConnectorField = Ext.extend(Ext.form.Hidden, {

	/**
	* @cfg {String} connectFormId Id of the Ext.form.FormPanel component this field should link to.
	*/
	connectFormId: null,

	/**
	* @cfg {Function} serializer Function to use to encode form field values into the returned field
	* value of this field. Defaults to Ext.encode
	*/
	serializer: Ext.encode,

	deserializer: Ext.decode,

	getConnectedFormHandler: function() {
		return Ext.getCmp(this.connectFormId).getForm();
	},

	getConnectedForm: function() {
		if(!this.connectedForm) {
			this.connectedForm = this.getConnectedFormHandler();
		}
		return this.connectedForm;
	},

	getValue: function() {
		var data = this.getConnectedForm().getFieldValues();
		return this.serializer(data);
	},

	getRawValue: function() {
		return this.getValue();
	},

	setValue: function(val) {
		var data = this.deserializer(val);
		this.getConnectedForm().setValues(data);
	}
});





/*
Ext.ux.RapidApp.errMsgHandler = function(title,msg) {
	Ext.Msg.show({
		title: title,
		msg: Ext.util.Format.nl2br(msg),
		buttons: Ext.Msg.OK,
		icon: Ext.Msg.ERROR,
		minWidth: 275
	});
}
*/


Ext.ux.RapidApp.errMsgHandler = function(title,msg,as_text) {
	var win;
	
	var body = as_text ? '<pre>' + Ext.util.Format.nl2br(msg) + '</pre>' : msg;
	
	win = new Ext.Window({
		manager: Ext.ux.RapidApp.CustomPromptWindowGroup,
    title: 'Exception',
		width: 600,
		height: 400,
		modal: true,
		closable: true,
		layout: 'fit',
		items: {
			xtype: 'panel',
			frame: true,
			headerCfg: {
				tag: 'div',
				cls: 'ra-exception-heading',
				html: title
			},
			autoScroll: true,
			html: '<div class="ra-exception-body">' + body + '</div>',
			bodyStyle: 'padding:5px;'
		},
		buttonAlign: 'center',
		buttons: [{
			text: 'Ok',
			handler: function() { win.close(); }
		}],
		listeners: {
			render: function(){
				// Catch navload events and auto-close the exception window:
				var loadTarget = Ext.getCmp("explorer-id").getComponent("load-target");
				if(loadTarget){
					loadTarget.on('navload',this.close,this);
					this.on('beforeclose',function(){
						loadTarget.un('navload',this.close);
					},this);
				}
			}
		}
	});
	win.show();
}


Ext.ux.RapidApp.showAjaxError = function(title,msg,options,data) {
  data = data || {};

  // -----------------------------------------------------------------------------
  // Check to see if this exception is associated with an AutoPanel load, and
  // if it is, display the exception message in the AutoPanel body instead of in
  // a new window
  if(options && options.scope && options.scope.AutoPanelId) {
    var AutoPanel = Ext.getCmp(options.scope.AutoPanelId);
    if(AutoPanel) {
      return AutoPanel.setErrorBody.call(AutoPanel,title,msg);
    }
  }
  // -----------------------------------------------------------------------------
  else {
    if (data.winform) {
      return Ext.ux.RapidApp.WinFormPost(data.winform);
    }
    else {
      return Ext.ux.RapidApp.errMsgHandler(title,msg,data.as_text);
    }
  
  }

}

Ext.ux.RapidApp.ajaxCheckException = function(conn,response,options) {
  if (!response || !response.getResponseHeader) return;

  try {
    var exception = response.getResponseHeader('X-RapidApp-Exception');
    if (exception) {
      var data = response.result || Ext.decode(response.responseText, true) || {};
      var title = data.title || 'Error';
      var msg = data.msg || 'unknown error - Ext.ux.RapidApp.ajaxCheckException';
      
      Ext.ux.RapidApp.showAjaxError(title,msg,options,data);
    }
    
    var warning = response.getResponseHeader('X-RapidApp-Warning');
    if (warning) {
      var data = Ext.decode(warning);
      var title = data.title || 'Warning';
      var msg = data.msg || 'Unknown (X-RapidApp-Warning)';
      Ext.ux.RapidApp.errMsgHandler(title,msg,data.as_text);
    }
    
    var eval_code = response.getResponseHeader('X-RapidApp-EVAL');
    if (eval) { eval(eval_code); }
  }
  catch(err) {}
}

Ext.ux.RapidApp.ajaxRequestContentType = function(conn,options) {
	if (!options.headers) { options.headers= {}; }
	options.headers['X-RapidApp-RequestContentType']= 'JSON';
  options.headers['X-RapidApp-VERSION'] = Ext.ux.RapidApp.VERSION;
};


Ext.ux.RapidApp.ajaxException = function(conn,response,options) {
  if (response && response.getResponseHeader) {
    if(response.getResponseHeader('X-RapidApp-Exception')) {
      // If this is an exception with the X-RapidApp-Exception header,
      // pass it off to the normal check exception logic
      return Ext.ux.RapidApp.ajaxCheckException.apply(this,arguments);
    }
    else {
      // If we're here, it means a raw exception was encountered (5xx) 
      // with an X-RapidApp-Exception header, so just throw the raw
      // response body as text. This should not happen - it probably means 
      // the server-side failed to catch the exception. The message will
      // probably be ugly, but it is the best/safest thing we can do at 
      // this point:
      return Ext.ux.RapidApp.showAjaxError(
        'Ajax Exception - ' + response.statusText + ' (' + response.status + ')',
        '<pre>' + Ext.util.Format.htmlEncode(response.responseText) + '</pre>'
      );
    }
  }
  else {
    // If we're here it means the request failed altogether, and didn't even
    // send back a response with headers (server is down, network down, etc).
    
    // If this is an AutoPanel load request, take no action, since AutoPanel
    // handles its own errors by showing them as its content: 
    // (TODO - consolidate this handling)
    if(options && options.scope && options.scope.AutoPanelId) {
      return;
    }
    
    // For all other types of Ajax requests (such as CRUD actions), display
    // the error to the user in a standard window:
    var msg = (response && response.statusText) ? 
      response.statusText : 'unknown error';
    return Ext.ux.RapidApp.showAjaxError(
      'Ajax Request Failed',
      '<div style="padding:10px;font-size:1.5em;color:navy;">&nbsp;&nbsp;' +
        '<b>' + msg + '</b>' +
      '&nbsp;</div>'
    );
  }
}

Ext.Ajax.on('requestexception',Ext.ux.RapidApp.ajaxException);
Ext.Ajax.on('requestcomplete',Ext.ux.RapidApp.ajaxCheckException);
Ext.Ajax.on('beforerequest',Ext.ux.RapidApp.ajaxRequestContentType);




Ext.ux.RapidApp.ajaxShowGlobalMask = function(conn,options) {
	if(options.loadMaskMsg) {
	
		conn.LoadMask = new Ext.LoadMask(Ext.getBody(),{
			msg: options.loadMaskMsg
			//removeMask: true
		});
		conn.LoadMask.show();
	}
}
Ext.ux.RapidApp.ajaxHideGlobalMask = function(conn,options) {
	if(conn.LoadMask) {
		conn.LoadMask.hide();
	}
}
Ext.Ajax.on('beforerequest',Ext.ux.RapidApp.ajaxShowGlobalMask,this);
Ext.Ajax.on('requestcomplete',Ext.ux.RapidApp.ajaxHideGlobalMask,this);
Ext.Ajax.on('requestexception',Ext.ux.RapidApp.ajaxHideGlobalMask,this);

Ext.ux.RapidApp.checkLocalTimezone = function(conn,options) {
	if (!options.headers) { options.headers= {}; }
	var dt= new Date();
	Ext.ux.RapidApp.userPrefs.timezoneOffset= -dt.getTimezoneOffset();
	options.headers['X-RapidApp-TimezoneOffset']= Ext.ux.RapidApp.userPrefs.timezoneOffset;
};
Ext.Ajax.on('beforerequest',Ext.ux.RapidApp.checkLocalTimezone);


Ext.Ajax.on('requestexception',function(conn,response,options){
	
	if(response && response.isTimeout){
		
		var timeout = options.timeout ? (options.timeout/1000) : null;
		timeout = timeout ? timeout : conn.timeout ? (conn.timeout/1000) : null;
		var msg = timeout ? 'Request Timed Out (' + timeout + ' secs).' : 'Request Timed Out.';
		
		Ext.Msg.show({
			title:'Timeout',
			msg: msg,
			icon: Ext.Msg.WARNING,
			buttons: Ext.Msg.OK
		});
	}
});


/* -------------------------------------------------------------------------------------
/* ------------------------------------------------------------------------------------- 
 This should be used instead of 'new Ext.data.Connection()' whenever creating a
 custom Conn object. The reason one might want to create a custom Conn object
 instead of using the Ext.Ajax singleton is to be able to set custom event listeners
 that apply to just that one connection. But we want these to also fire the global
 RapidApp event listeners, too:                                                         */
Ext.ux.RapidApp.newConn = function(config) {
	
	config = config || {};
	
	// Copy default properties from Ext.Ajax
	var props = Ext.copyTo({},Ext.Ajax,[
		'autoAbort',
		'disableCaching',
		'disableCachingParam',
		'timeout'
	]);
	Ext.apply(props,config);
	
	var Conn = new Ext.data.Connection(props);
	
	// Relay all the events of Ext.Ajax:
	Ext.Ajax.relayEvents(Conn,[
		'beforerequest',
		'requestexception',
		'requestcomplete'
	]);
	
	return Conn;
};
/* -------------------------------------------------------------------------------------
/* -------------------------------------------------------------------------------------
/* ------------------------------------------------------------------------------------- */


/**
 * This function performs special handling for custom HTTP headers (or in the case of form uploads, custom
 *   JSON attributes) which may either continue the current request, or end it, or restart it with additional
 *   parameters.
 * This is called by our overridden Ext.data.Connection.handleResponse, and our custom
 *   Ext.data.Connection.doFormUpload.
 */
Ext.ux.RapidApp.handleCustomServerDirectives= function(response, continue_current_callback, success_callback_repeat) {
	var auth = response.getResponseHeader('X-RapidApp-Authenticated');
	if (auth != null)
		if (!Ext.ux.RapidApp.updateAuthenticated(auth, success_callback_repeat))
			return;
	
	var customprompt = response.getResponseHeader('X-RapidApp-CustomPrompt');
	if (customprompt)
		return Ext.ux.RapidApp.handleCustomPrompt(customprompt,success_callback_repeat);
	
	// If it was an exception, it got handled/displayed already in ajaxCheckException, so don't process further.
	if(response.getResponseHeader('X-RapidApp-Exception'))
		return;
	
	continue_current_callback();
	
	var servercallback = response.getResponseHeader('X-RapidApp-Callback');
	if (servercallback) {
		// Put the response into "this" and then call the callback handler with "this" scope
		this.response = response;
		Ext.ux.RapidApp.handleServerCallBack.call(this,servercallback);
	}
	
	var serverevents = response.getResponseHeader('X-RapidApp-ServerEvents');
	if (serverevents) {
		Ext.ux.RapidApp.handleServerEvents(serverevents);
	}
}

Ext.ux.RapidApp.handleServerEvents = function(headerdata) {
	var events = Ext.decode(headerdata);
	Ext.ux.RapidApp.EventObject.Fire.apply(Ext.ux.RapidApp.EventObject,events);
}

// returns whether or not to keep processing the request
Ext.ux.RapidApp.updateAuthenticated= function(authValue, success_callback_repeat) {
	var orig = Ext.ux.RapidApp.Authenticated;
	if (authValue != '0') { Ext.ux.RapidApp.Authenticated = authValue; }
	if (orig && orig != authValue && authValue == '0') {
		Ext.ux.RapidApp.ReAuthPrompt(success_callback_repeat);
		return false;
	}
	return true;
}


// Call an arbitrary function specified in the response from the server (X-RapidApp-Callback)
// If "scoped" is true, the function is called with the scope (this) of the Ext.data.Connection 
// that made the Ajax request to the server, and the response is available in 'this.response'
Ext.ux.RapidApp.handleServerCallBack = function(headerdata) {

	var data = {};
	Ext.apply(data,Ext.decode(headerdata));
	
	if (! data.func && ! data.anonfunc) {
		throw "Neither 'func' nor 'anonfunc' was specified in X-RapidApp-Callback header data";
	}
	
	var arr_to_param_str = function(name,arr) {
		var str = '';
		Ext.each(arr,function(item,index) {
			str += name + '[' + index + ']';
			if (arr.length > index + 1) {
				str += ',';
			}
		});
		return str;
	}
	
	var arg_str = '';
	if (data.arguments) {
		arg_str = arr_to_param_str('data.arguments',data.arguments);
	}
	
	var anonfunc;
	if (data.anonfunc && ! data.func) {	
		eval('anonfunc = ' + data.anonfunc + ';');
		data.func = 'anonfunc';
	}
	
	var func;
	if (data.scoped) {
		var scope = this;
		eval('func = function() { return ' + data.func + '.call(scope,' + arg_str + '); };'); 
	}
	else {
		eval('func = function() { return ' + data.func + '(' + arg_str + '); };'); 
	}
	return func();
}

Ext.ux.RapidApp.handleCustomPrompt = function(headerdata,success_callback) {

	var win;
	
	// Defaults
	var data = {
		title: 'Untitled X-RapidApp-CustomPrompt',
		param_name: 'customprompt',
		height: 300,
		width: 400,
		buttons: ['Ok'],
		buttonIcons: {},
		items: [
			{
				xtype: 'label',
				html: 'No data available'
			}
		]
	};
	
	var default_formpanel_cnf = {
		itemId: 'formpanel',
		frame: true,
		labelAlign: 'right',
		bodyStyle: 'padding:20px 20px 10px 10px;',
		labelWidth: 70,
		defaults: {
			xtype: 'textfield',
			width: 175
		}
	};
	
	Ext.apply(data,Ext.decode(headerdata));
	if(! data.formpanel_cnf) { data.formpanel_cnf = {}; }
	
	if(data.validate) {
		default_formpanel_cnf.monitorValid = true;
	}
	
	
	Ext.apply(default_formpanel_cnf,data.formpanel_cnf);
	data.formpanel_cnf = default_formpanel_cnf;
	
	var btn_handler = function(btn) {
		
		win.callingHandler = true;
		
		var formpanel = win.getComponent('formpanel');
		if(!formpanel) { return; }
		
		var form = formpanel.getForm();
		var data = form.getFieldValues();
		
		var headers = {
			'X-RapidApp-CustomPrompt-Button': btn.text,
			'X-RapidApp-CustomPrompt-Data': Ext.encode(data)
		};
		
		// Recall the original request, adding in the customprompt header ata:
		var newopts = { headers: headers };
		//btn.ownerCt.ownerCt.close();
		win.close();
		return formpanel.success_callback(newopts);
	}
	
	var onEsc = null;
	
	// Custom buttons:
	var buttons = [];
	Ext.each(data.buttons,function(text) {
		var btn = {
			xtype: 'button',
			text: text,
			handler: btn_handler
		}
		
		if(data.EnterButton && data.EnterButton == text) {
			
			var click_fn = btn_handler.createCallback({text: text});
			
			btn.listeners = {
				click: click_fn,
				afterrender: function(b) {
					var fp = b.ownerCt.ownerCt;
					
					new Ext.KeyMap(fp.el, {
						key: Ext.EventObject.ENTER,
						shift: false,
						alt: false,
						fn: function(){ this.el.dom.click(); },
						scope: b
					});
					
				}
			}
		}
		
		if(data.EscButton && data.EscButton == text) {
			onEsc = btn_handler.createCallback({text:text});
		}
		else if(data.validate) {
			btn.formBind = true;
		}
		
		if(data.buttonIcons[text]) {
			btn.iconCls = data.buttonIcons[text];
		}
		
		buttons.push(btn);
	});
	
	// Cancel:
	if(!data.noCancel) {
		buttons.push({
			xtype: 'button',
			text: 'Cancel',
			handler: function(btn) {
				//btn.ownerCt.ownerCt.close();
				win.close();
			}
		});
	}
	
		
	var formpanel = {
		xtype: 'form',
		itemId: 'formpanel',
		autoScroll: true,
		//anchor: '100% 100%',
		items: data.items,
		buttons: buttons,
		success_callback: success_callback // <-- storing this here so we can use it in the btn handler
	};
	
	Ext.apply(formpanel,data.formpanel_cnf);
	
	var window_cnf = {
		manager: Ext.ux.RapidApp.CustomPromptWindowGroup,
		title: data.title,
		layout: 'fit',
		width: data.width,
		height: data.height,
		closable: true,
		modal: true,
		items: formpanel,
		listeners: {
			afterrender: function(w) {
				if(!data.focusField) { return; }
				var fp = w.getComponent('formpanel');
				var field = fp.getForm().findField(data.focusField);
				if(field) { field.focus('',10); field.focus('',200); field.focus('',500); }
			},
			beforeclose: function(w){
				if(onEsc && !w.callingHandler) { 
					w.callingHandler = true; 
					onEsc(); 
				}
			}
		}
	};
	
	if(data.noCancel && !onEsc) { window_cnf.closable = false; }

	win = new Ext.Window(window_cnf);
	win.show();
};




//Default for RapidApp::AuthCore plugin:
Ext.ux.RapidApp.loginUrl = '/auth/reauth';

Ext.ux.RapidApp.ReAuthPrompt = function(success_callback) {

	 var fieldset = {
		xtype: 'fieldset',
		style: 'border: none',
		hideBorders: true,
		labelWidth: 80,
		border: false,
		defaults: {
			xtype: 'textfield',
			labelStyle: 'text-align:right'
		},
		items: [
			{
				 xtype: 'label',
				 text: 'Your session has expired or is invalid. Please re-enter your password below:'
			},
			{
				 xtype: 'spacer',
				 height: 15
			},
			{
				name: 'username',
				fieldLabel: 'username',
				value: Ext.ux.RapidApp.Authenticated,
				readOnly: true,
				style: {
					/*
						the normal text field has padding-top: 2px which makes the text sit towards
						the bottom of the field. We set top and bottom here to move one of the px to the
						bottom so the text will be vertically centered but take up the same vertical
						size as a normal text field:
					*/
					'background-color': 'transparent',
					'border-color': 'transparent',
					'background-image':'none',
					'padding-top':'1px',
					'padding-bottom':'1px'
				}
			},
			{
				name: 'password',
				fieldLabel: 'password',
				inputType: 'password',
				listeners: {
					afterrender: function(field) {
						field.focus('',10);
						field.focus('',200);
						field.focus('',500);
					}
				 }
			}
		]
	};

	Ext.ux.RapidApp.WinFormPost({
    manager: Ext.ux.RapidApp.CustomPromptWindowGroup,
		title: "Session Expired",
		height: 220,
		width: 300,
		url: Ext.ux.RapidApp.loginUrl,
		fieldset: fieldset,
		closable: false,
		submitBtnText: 'Login',
		success: function(response,opts) {
			//var res = Ext.decode(response.responseText);
			//if (res.success == 0) {
			//	Ext.ux.RapidApp.ReAuthPrompt();
			//}
			if(success_callback) return success_callback();
		},
		failure: function() {
			 Ext.ux.RapidApp.ReAuthPrompt();
		},
		cancelHandler: function() {
			window.location.reload();
		}
	});
}

Ext.ux.RapidApp.validateCsvEmailStr = function(v) {
	var str = new String(v);
	var arr = str.split(',');

	for (i in arr) {
		var email = arr[i];
		// For some stupid reason the last arg returned from split is a function! So we have to do this:
		if(typeof(email) == 'string') {
			var trimmed = Ext.util.Format.trim(email);
			var result = Ext.form.VTypes.email(trimmed);
			if(! result) return false;
		}
	}

	return true;
}



Ext.ux.RapidApp.CustomPickerField = Ext.extend(Ext.form.TriggerField, {

	nodeProperty: 'dataValue',
	
	buttonAlign: 'right',
	
	afterSelectHandler: Ext.emptyFn,
	afterSelectHandler_scope: null,
	
	initComponent: function() {
		
		// Handle an initial value:
		if(this.value) {
			var init_value = this.value.valueOf();
			delete this.value;
			this.on('afterrender',function(cmp) {
				cmp.setValue(init_value);
			});
		}
		
		this.buttons = [
			{
				text: 'Select',
				itemId: 'select',
				handler: function(btn) {
					var app = btn.ownerCt.ownerCt.getComponent('app').items.first();

					var data = this.select_handler(app);
					if(data === false) { return; }

					this.dataValue = data.value;

					this.setValue(data.display);
					btn.ownerCt.ownerCt.close();
					
					var scope = this;
					if(this.afterSelectHandler_scope) { scope = this.afterSelectHandler_scope; }
					this.afterSelectHandler.call(scope,data,app,arguments);
				},
				scope: this
			},
			{
				text: 'Cancel',
				handler: function(btn) {
					btn.ownerCt.ownerCt.close();
				}
			}
		];
		
		//this.setEditable(false);
				//console.dir(this);
		//this.constructor.superclass.constructor.prototype.initComponent.apply(this, arguments);
		Ext.ux.RapidApp.CustomPickerField.superclass.initComponent.apply(this, arguments);
	},

	getValue: function() {
		if (this.dataValue) { return this.dataValue; }
		return Ext.ux.RapidApp.CustomPickerField.superclass.getValue.apply(this, arguments);
	},

	setValue: function(val) {
		var new_val = val;
		if(this.setValue_translator) {
			new_val = this.setValue_translator(val,this);
		}
		Ext.ux.RapidApp.CustomPickerField.superclass.setValue.call(this, new_val);
	},

	getAutoLoad: function() {
		var autoLoad = {
			url: this.load_url
		};

		if (this[this.nodeProperty]) {
			autoLoad.params = {
				node: this[this.nodeProperty]
			};
		}
		
		return autoLoad;
	},
	
	getPickerApp: function() {
		var autoLoad = this.getAutoLoad();
		return {
			xtype: 'autopanel',
			itemId: 'app',
			autoLoad: autoLoad,
			layout: 'fit'
		};
	},
	
	onTriggerClick: function() {
		var win = new Ext.Window({
			Combo: this,
			buttonAlign: this.buttonAlign,
			title: this.win_title,
			layout: 'fit',
			width: this.win_width,
			height: this.win_height,
			closable: true,
			modal: true,
			items: this.getPickerApp(),
			buttons: this.buttons
		});

		win.show();
	}


});
Ext.reg('custompickerfield',Ext.ux.RapidApp.CustomPickerField);



Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.confirmDialogCall = function(title,msg,fn,params) {

	var args = Array.prototype.slice.call(arguments);

	var title = args.shift();
	var msg = args.shift();
	var fn = args.shift();

	return Ext.Msg.show({
		title: title,
		msg: msg,
		buttons: Ext.Msg.YESNO, fn: function(sel) {
			if (sel != 'yes') return;
			fn(args);
		},
		scope: this
	});
}



/* http://mentaljetsam.wordpress.com/2008/06/02/using-javascript-to-post-data-between-pages/ */
Ext.ns('Ext.ux.postwith');
Ext.ux.postwith = function (to,p) {
	var myForm = document.createElement("form");
	myForm.method="post" ;
	myForm.action = to ;
	for (var k in p) {
		var myInput = document.createElement("input") ;
		myInput.setAttribute("name", k);
		myInput.setAttribute("value", p[k]);
		myForm.appendChild(myInput);
	}
	document.body.appendChild(myForm);
	myForm.submit();
	document.body.removeChild(myForm);
};


// http://thomas.bindzus.me/2007/12/24/adding-dynamic-contents-to-iframes/
Ext.ns('Ext.ux.IFrame');
Ext.ux.IFrame = function (parentElement) {

   // Create the iframe which will be returned
   var iframe = document.createElement("iframe");
 
   // If no parent element is specified then use body as the parent element
   if(parentElement == null)
      parentElement = document.body;
 
   // This is necessary in order to initialize the document inside the iframe
   parentElement.appendChild(iframe);
 
   // Initiate the iframe's document to null
   iframe.doc = null;
 
   // Depending on browser platform get the iframe's document, this is only
   // available if the iframe has already been appended to an element which
   // has been added to the document
   if(iframe.contentDocument)
      // Firefox, Opera
      iframe.doc = iframe.contentDocument;
   else if(iframe.contentWindow)
      // Internet Explorer
      iframe.doc = iframe.contentWindow.document;
   else if(iframe.document)
      // Others?
      iframe.doc = iframe.document;
 
   // If we did not succeed in finding the document then throw an exception
   if(iframe.doc == null)
      throw "Document not found, append the parent element to the DOM before creating the IFrame";
 
   // Create the script inside the iframe's document which will call the
   iframe.doc.open();
   iframe.doc.close();
 
   // Return the iframe, now with an extra property iframe.doc containing the
   // iframe's document
   return iframe;
};

Ext.ns('Ext.ux.iFramePostwith');
Ext.ux.iFramePostwith = function (to,p) {
	
	// TODO: in order to detect the completion of the submit there will
	// need to be a server-side process to return js code with an 'onload'
	// event. In the mean time, we don't clean up ourselves, but we do
	// look for and cleanup previous calls. This is a hack-ish workaround
	var id = 'iframe-poster-global-element';
	var old_iframe = document.getElementById(id);
	if(old_iframe){
		document.body.removeChild(old_iframe);
	}
	
	var iframe = new Ext.ux.IFrame(document.body);
	iframe.id = id;
	
	var myForm = iframe.doc.createElement("form");
	myForm.method="post" ;
	myForm.action = to ;

	for (var k in p) {
		var v = (p[k] == null || typeof p[k] == 'undefined') ? '' : p[k];
		var myInput = iframe.doc.createElement("input") ;
		myInput.setAttribute("name", k);
		myInput.setAttribute("value", v);
		myForm.appendChild(myInput) ;
	}
	iframe.doc.body.appendChild(myForm) ;
	myForm.submit() ;
}


/* ####################################################### */
/* ####################################################### */

/*
 --- http://encosia.com/ajax-file-downloads-and-iframes/ ---
 This assumes that the content returned from 'url' will be "Content-disposition: attachment;"
 The purpose is to allow a background download operation that won't be
 cancelled if the user clicks around the app before the response comes back, (which 
 happens with Ext.ux.postwith) and also won't navigate the page if an error occurs during 
 the download. The downside of this is that nothing will be shown to the user if an error or 
 exception occurs, the download will just never happen. To address this limitation, see the 
 alternate method 'Ext.ux.RapidApp.winDownload' below (which has its own, different limitations)
 
 UPDATE: one limitation of this function is with long URLs since it uses a GET instead of a 
 POST. This will fail if the encoded URL is longer than ~2k characters
*/
Ext.ns('Ext.ux.iframeBgDownload');
Ext.ux.iframeBgDownload = function (url,params,timeout) {
	var timer, timeout = timeout || Ext.Ajax.timeout;
	
	if(params) { url += '?' + Ext.urlEncode(params); }
	
	var iframe = document.createElement("iframe");
	
	var cleanup = function() {
		if(timer) { timer.cancel(); } //<-- turn off the timeout timer
		var task = new Ext.util.DelayedTask(function(){
			document.body.removeChild(iframe);
		});
		// give the download dialog plenty of time to be displayed before we
		// remove the iframe:
		task.delay(2000); 
	};
	
	// Start the fail-safe timeout timer:
	// (we need this because we have no way of detecting an exception in the 
	// iframe load)
	timer = new Ext.util.DelayedTask(cleanup);
	timer.delay(timeout);
	
	// This event only gets fired in FireFox (12) for file downloads. IE and 
	// Chrome have to wait for the timeout, which is lame and sucks.
	iframe.onload = cleanup; //<-- cleanup as soon as the iframe load completes
	
	iframe.style.display = "none";
	iframe.src = url;
	document.body.appendChild(iframe); 
}

/*
 This is an alternative to Ext.ux.iframeBgDownload above that displays the download
 interactively in an Ext Window containing an iframe performing the download,
 with a nice loading indicator. In the event of an error or exception from the
 server side, the error output is displayed inline in the iframe.

 This function would be great if only it worked properly in IE and Chrome. It
 works great in FireFox (12), but in other browsers the iframe onload event isn't
 fired if the src is a file download. In those cases, the user has to close the
 download box manually after they receive the file. This is the tradeoff for having
 feedback on processing and errors. If that isn't worth it, and you are OK doing the
 download in the background and discard errors, use Ext.ux.iframeBgDownload instead.
*/
Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.winDownload = function (url,params,msg,timeout) {
	var timer, timeout = timeout || Ext.Ajax.timeout;
	msg = msg || 'Downloading File...';
	
	if(params) { url += '?' + Ext.urlEncode(params); }
	
	var win;
	
	var iframe = document.createElement("iframe");
	iframe.height = '100%';
	iframe.width = '100%';
	iframe.setAttribute("frameborder", '0');
	iframe.setAttribute("allowtransparency", 'true');
	iframe.src = url;
	
	var cleanup = function(){
		if(timer) { timer.cancel(); } //<-- turn off the timeout timer
		if(!win) { return; }
		win.hide(); // <-- hide immediately
		
		var task = new Ext.util.DelayedTask(function(){
			win.close()
		});
		// give the download dialog plenty of time to be displayed before we
		// actually close/destroy the window and iframe:
		task.delay(2000); 
	};
	
	// Unfortunately, this event is only fired in FireFox if it is a
	// file download. In IE and Chrome, it never gets fired and so the
	// window never gets hidden. The user has to close the dialog box
	// themselves.
	iframe.onload = cleanup;
	
	// Silently close the window after timeout. TODO: add an option to
	// update the window/iframe contents with a message instead. That would
	// only be useful in FireFox, since in other browsers we have no way of
	// knowing if the download was successful once the timeout is reached.
	timer = new Ext.util.DelayedTask(cleanup);
	timer.delay(timeout);
	
	win = new Ext.Window({
		title: msg,
		modal: true,
		closable: false,
		width: 400,
		height: 225,
		bodyCssClass: 'loading-background',
		buttonAlign: 'center',
		buttons:[{
			width: 150,
			text: 'Close',
			iconCls: 'ra-icon-cross',
			handler: function(){ win.hide(); win.close(); }
		}],
		listeners: {
			beforeclose: function(){
				if(timer) { timer.cancel(); } //<-- turn off the timeout timer
			}
		},
		contentEl: iframe
	});

	win.show();
}

/*
 Another, simple download function but uses a self-closing (browser) window. 
 Again, assumes url is a file download. This is just left in for reference, 
 because it is rough looking. See Ext.ux.RapidApp.winDownload above which uses 
 an Ext Window and has better error handling and control
*/
Ext.ns('Ext.ux.winIframeDownload');
Ext.ux.winPostwith = function (url,params) {
	if(params) { url += '?' + Ext.urlEncode(params); }
	return window.open(
		url,"winDownload", 
		"height=100,width=200," +
		"menubar=no,status=no,location=no,toolbar=no,resizable=no"
	);
}
/* ####################################################### */
/* ####################################################### */


Ext.ns('Ext.ux.Bool2yesno');
Ext.ux.Bool2yesno = function(val) {
	if (val == null || val === "") { return Ext.ux.showNull(val); }
	if (val > 0) { return 'Yes'; }
	return 'No';
}


Ext.ns('Ext.ux.showNull');
Ext.ux.showNull = function(val) {
	if (val == null) { return '<span style="color:darkgrey;">(not&nbsp;set)</span>'; }
	if (val === "") { return '<span style="color:darkgrey;">(empty&nbsp;string)</span>'; }
	return val;
}

Ext.ns('Ext.ux.showNullusMoney');
Ext.ux.showNullusMoney = function(val) {
	if (val == null || val === "") { return Ext.ux.showNull(val); }
	return Ext.util.Format.usMoney(val);
}


/*
Ext.ux.RapidApp.WinFormPost

 * @cfg {String} title Window title
 * @cfg {String} height Window height
 * @cfg {String} width Window width
 * @cfg {Object} fieldset form config
 * @cfg {String} url URL to post to
 * @cfg {Object} params base params to submit with
 * @cfg {Boolean} encode_values true to encode the form data in JSON
 * @cfg {Object} valuesParamName POST param to store JSON serialized form data in
 * @cfg {Function} success success callback function
 * @cfg {Function} failure failure callback function
 * @cfg {Boolean} eval_response if true the response will be evaled
 * @cfg {Boolean} disableBtn disables the button once clicked - note:
                             uncaught exceptions from the server will
                             cause the button to never be re-enabled
 * @cfg {String} disableBtnText Text to set in the 'Save' button when disabled
*/
Ext.ns('Ext.ux.RapidApp.WinFormPost');
Ext.ux.RapidApp.WinFormPost = function(cfg) {

  var rand = Math.floor(Math.random()*100000);
  var winId = 'win-' + rand;
  var formId = 'winformpost-' + rand;
  
  Ext.applyIf(cfg,{
    title: '',
    height: 400,
    width: 350,
    params: {},
    valuesParamName: 'json_form_data',
    submitBtnText: 'Save',
    cancelHandler: Ext.emptyFn,
    closable: true,
    disableBtnText: 'Wait...',
  });
  
	var cancel_fn = function(){ Ext.getCmp(winId).close(); cfg.cancelHandler(); }
	
	cfg.fieldset['anchor'] = '100% 100%';

	var scope = this;

	var success_fn = function(response,options) {
		Ext.getCmp(winId).close();
		// Call the success function if it was passed in the cfg:
		if (cfg.success) { cfg.success.apply(scope,arguments); }
		
		var call_args = arguments;
		
		// Call additional specified success callbacks. These can be functions outright,
		// or objects containing a custom scope and handler:
		if(Ext.isArray(cfg.success_callbacks)) {
			Ext.each(cfg.success_callbacks,function(item) {
				if(Ext.isFunction(item)) {
					//call the function with the same scope as
					item.apply(scope,call_args);
				}
				else if(Ext.isObject(item)) {
					if(item.scope && item.handler) {
						//call the handler with the custom provided scope:
						item.handler.apply(item.scope,call_args);
					}
				}
			});
		}
		
		if (cfg.eval_response && response.responseText) { return eval(response.responseText); }
	};
  
  var Btn;
  Btn = new Ext.Button({
    text	: cfg.submitBtnText,
    handler	: function(btn) {
      if(cfg.disableBtn) {
        btn.setDisabled(true);
        btn.setText(cfg.disableBtnText);
      }
      
      var form = Ext.getCmp(formId).getForm();

      if (cfg.useSubmit) {
        return form.submit({
          url: cfg.url,
          params: cfg.params,
          success: success_fn,
          failure: failure_fn
        });
      }
      else {

        var values;
        if (cfg.noRaw) {
          values = form.getFieldValues();
        }
        else {
          values = form.getValues();
        }

        var params = cfg.params;
        if (cfg.encode_values) {
          params[cfg.valuesParamName] = Ext.util.JSON.encode(values);
        }
        else {
          for (i in values) {
            if(!params[i]) { params[i] = values[i]; }
          }
        }

        return Ext.Ajax.request({
          url: cfg.url,
          params: params,
          success: success_fn,
          failure: failure_fn
        });
      }
    }
  });

	var failure_fn = function(response,options) {
    // Re-enable the button (only applies with disableBtn option)
    Btn.setDisabled(false);
    Btn.setText(cfg.submitBtnText);
    if (cfg.failure) { cfg.failure.apply(scope,arguments); }
  };

	var win = new Ext.Window({
		manager: cfg.manager,
    title: cfg.title,
		id: winId,
		layout: 'fit',
		width: cfg.width,
		height: cfg.height,
		closable: cfg.closable,
		modal: true,
		items: {
			xtype: 'form',
			anchor : cfg.fieldset['anchor'],
			id: formId,
			frame: true,
			items: cfg.fieldset,
			fileUpload: cfg.fileUpload,
			baseParams: cfg.baseParams,
			buttons: [
				Btn,
				{
					text		: 'Cancel',
					handler	: cancel_fn
				}
			],
			listeners: {
				afterrender: function(fp) {
					new Ext.KeyMap(fp.el, {
						key: Ext.EventObject.ENTER,
						shift: false,
						alt: false,
						fn: function(keyCode, e){
								if(e.target.type === 'textarea' && !e.ctrlKey) {
									return true;
								}
								this.el.select('button').item(0).dom.click();
								return false;
						},
						scope: this
					});
				}
			}
		}
	});
	win.show();
}





Ext.ns('Ext.ux.EditRecordField');
Ext.ux.EditRecordField = function(config) {

	var rand = Math.floor(Math.random()*100000);
	var winId = 'win-' + rand;
	var formId = 'editrec-' + rand;
	var minFieldWidth = 175;

	var win_init_w = 200;
	var win_init_h = 100;

	var field = {
		xtype		: 'textfield',
		hideLabel	: true
	};

	if (config.fieldType) { field['xtype'] = config.fieldType; }

	if (config.field_cnf) { //<-- field_cnf override
		field = config.field_cnf;

		// -----------------
		if (field['xtype'] == 'fieldset') { return Ext.ux.EditRecordFieldSet(config.Record,field); }
		// -----------------

		if (field['width']) {
			win_init_w = field['width'] + 100;
			delete field['width'];
		}
	}

	field['value'] = config.Record.data[config.fieldName];
	field['save_field_name'] = config.fieldName;
	if (config.save_field_name) { field['save_field_name'] = config.save_field_name; }

	if (config.fieldType && !field['xtype']) { field['xtype'] = config.fieldType; }
	if (config.fieldName && !field['name']) { field['name'] = config.fieldName; }
	if (config.monitorValid && !field['monitorValid']) { field['monitorValid'] = config.monitorValid; }

	if (!field['id']) { field['id'] = 'field-' + rand; }

	//field['value'] = record_val;
	//if (config.initValue) { field['value'] = config.initValue; } //<-- this is needed for certain combo fields

	field['anchor'] = '100%';
	if (field['xtype'] == 'textarea') {
		field['anchor'] = '100% 100%';
	}

	var win = new Ext.Window({
		id: winId,
		width: win_init_w,
		height: win_init_h,
		layout: 'fit',
		title: config.fieldLabel + ':',
		modal: true,
		items: {
			xtype: 'form',
			anchor : field['anchor'],
			id: formId,
			frame: true,
			items: field,
			buttons: [
				{
					text		: 'Save',
					handler	: function() {
						var oField = Ext.getCmp(field['id']);
						var cur_val = oField.getValue();
						config.Record.set(field['save_field_name'],cur_val);
						config.Record.store.save();
						Ext.getCmp(winId).close();
					}
				},
				{
					text		: 'Cancel',
					handler	: function() {
						Ext.getCmp(winId).close();
					}
				}
			]
		},
		listeners: {
			afterrender: function(win) {
				var oField = Ext.getCmp(field['id']);
				if (!config.field_cnf) { //<-- don't run text metrics if there is a cust field_cnf

					var TM = Ext.util.TextMetrics.createInstance(oField.el);
					var wid;
					if (oField.getXType() == 'textarea') {
						wid = 400;
						TM.setFixedWidth(wid);
						var hig = TM.getHeight(field['value']) + 20;
						if (hig < 300) { hig = 300; }
						if (hig > 600) { hig = 600; }

						win.setHeight(hig);
					}
					else {
						wid = TM.getWidth(field['value']) + 50;
					}

					if (wid > 500) { wid = 500; }

					if (wid > minFieldWidth) {
						win.setWidth(wid);
					}
				}
			}
		}
	});
	win.show();
}



Ext.ns('Ext.ux.EditRecordFieldSet');
Ext.ux.EditRecordFieldSet = function(Record,fieldset) {

	var rand = Math.floor(Math.random()*100000);
	var winId = 'win-' + rand;
	var formId = 'editrec-' + rand;
	var minFieldWidth = 175;

	var win_init_w = 550;
	var win_init_h = 200;

	for (i in fieldset.items) {
		fieldset.items[i]['value'] = Record.data[fieldset.items[i]['name']];
		if (!fieldset.items[i]['save_field_name']) { fieldset.items[i]['save_field_name'] = fieldset.items[i]['name']; }
		if (!fieldset.items[i]['id']) { fieldset.items[i]['id'] = 'field-' + i + '-' + rand; }
	}

	fieldset['anchor'] = '100% 100%';

	var win = new Ext.Window({
		id: winId,
		width: win_init_w,
		height: win_init_h,
		layout: 'fit',
		//title: 'FIELDSET ' + fieldset.fieldLabel + ':',
		modal: true,
		items: {
			xtype: 'form',
			anchor : fieldset['anchor'],
			id: formId,
			frame: true,
			items: fieldset,
			buttons: [
				{
					text	: 'Save',
					handler	: function() {

						for (i in fieldset.items) {

							var oField = Ext.getCmp(fieldset.items[i]['id']);
							if (oField) {
								try {
									var cur_val = oField.getValue();
									if (cur_val != fieldset.items[i]['value']) {
										Record.set(fieldset.items[i]['save_field_name'],cur_val);
									}
								} catch (err) {}
							}

						}

						Record.store.save();
						Ext.getCmp(winId).close();
					}
				},
				{
					text		: 'Cancel',
					handler	: function() {
						Ext.getCmp(winId).close();
					}
				}
			]
		}
	});
	win.show();
}






Ext.ns('Ext.ux.Msg.EditRecordField');
Ext.ux.Msg.EditRecordField = function(config) {

	var msgCnf = {
		prompt: true,
		title: config.fieldLabel + ':',
		//msg: config.fieldLabel + ':',
		buttons: Ext.MessageBox.OKCANCEL,
		fn: function(btn,text) {
			if (btn == 'ok') {
				config.Record.set(config.fieldName,text);
				config.Record.store.save();
			}
		},
		value: config.Record.data[config.fieldName],
		width: 250
	}

	if (config.fieldType == 'textarea') {
		msgCnf['width'] = 350;
		msgCnf['multiline'] = 200;
	}

	Ext.Msg.show(msgCnf);
}






Ext.ns('Ext.ux.FindNodebyId');
Ext.ux.FindNodebyId = function(node,id) {
	this.node = node;
	this.id = id;

	alert(this.node.id);
	if (this.node.id == this.id) { return this.node; }
	//if (this.node.isLeaf()) { return false; }

	if (this.node.childNodes) {
		for ( var i in this.node.childNodes ) {
			var child = this.node.childNodes[i];
			var checknode = Ext.ux.FindNodebyId(child,this.id);
			if (checknode) { return checknode; }
		}
	}
	return false;
}


Ext.ns('Ext.ux.FetchEval');
Ext.ux.FetchEval = function(url,params) {
	if (!params) { params = {}; }
	Ext.Ajax.request({
		disableCaching: true,
		url: url,
		params: params,
		success: function(response, opts) {
			if(response.responseText) { return eval(response.responseText); }
		},
		failure: function(response, opts) {
			alert('Ext.ux.FetchEval (' + url + ') AJAX request failed.' );
		}
	});
}






/* This is crap since 'anchor' exists
Ext.override(Ext.form.FormPanel, {
	plugins: [new Ext.ux.form.FieldAutoExpand()]
});
*/

/*
Ext.override(Ext.BoxComponent, {
	initComponent: function() {

		var thisC = this;
		if (thisC.autoLoadJsonConf) {


			Ext.Ajax.request({
				disableCaching: true,
				url: thisC.autoLoadJsonConf['url'],
				params: thisC.autoLoadJsonConf['params'],
				success: function(response, opts) {

					alert(response.responseText);

					var imported_data = Ext.util.JSON.decode(response.responseText);
					//var imported_data = eval('(' + response.responseText + ')');
					Ext.apply(this, Ext.apply(this.initialConfig, imported_data));
				},
				failure: function(response, opts) {
					alert('AJAX autoLoadJsonConf FAILED!!!!!!');
				}
			});
		}
		Ext.BoxComponent.superclass.initComponent.apply(this, arguments);
	}
});



Ext.override(Ext.BoxComponent, {
	initComponent: function() {

		var thisC = this;

		if (Ext.isArray(this.items)) {
			for (i in this.items) {
				if(this.items[i]['autoLoadJsonConf']) {
					var urlspec = this.items[i]['autoLoadJsonConf'];

					Ext.Ajax.request({
						disableCaching: true,
						url: urlspec['url'],
						params: urlspec['params'],
						success: function(response, opts) {

							alert(response.responseText);

							var imported_data = eval('(' + response.responseText + ')');
							thisC.insert(i,imported_data);
							thisC.doLayout();

							Ext.apply(this, Ext.apply(this.initialConfig, imported_data));
						},
						failure: function(response, opts) {
							alert('AJAX autoLoadJsonConf FAILED!!!!!!');
						}
					});

					delete this.items[i];


				}
			}
		}







		var thisC = this;
		if (thisC.autoLoadJsonConf) {


			Ext.Ajax.request({
				disableCaching: true,
				url: thisC.autoLoadJsonConf['url'],
				params: thisC.autoLoadJsonConf['params'],
				success: function(response, opts) {

					alert(response.responseText);

					var imported_data = Ext.util.JSON.decode(response.responseText);
					//var imported_data = eval('(' + response.responseText + ')');
					Ext.apply(this, Ext.apply(this.initialConfig, imported_data));
				},
				failure: function(response, opts) {
					alert('AJAX autoLoadJsonConf FAILED!!!!!!');
				}
			});
		}
		Ext.BoxComponent.superclass.initComponent.apply(this, arguments);
	}
});

*/


Ext.ux.DynContainer = Ext.extend(Ext.Container, {

	initComponent: function() {

		var id = this.id;
		var imported_data;
		var thisC = this;
		//if (thisC.itemsurl) {
		var config = {
			loadData: function(loadurl,params) {

				//alert('Loading: ' + loadurl);

				Ext.Ajax.request({
					disableCaching: true,
					//url: thisC.itemsurl,
					url: loadurl,
					params: params,
					success: function(response, opts) {

						imported_data = eval('(' + response.responseText + ')');

						thisC.removeAll();
						thisC.add(imported_data);
						thisC.doLayout();

					},
					failure: function(response, opts) {
						alert('AJAX FAILED!!!!!!');
					}
				});
			}
		};
		Ext.apply(this, Ext.apply(this.initialConfig, config));
		Ext.ux.DynContainer.superclass.initComponent.apply(this, arguments);
		//}
	},
	onRender: function() {
		Ext.ux.DynContainer.superclass.onRender.apply(this, arguments);
		var params = {};
		if(this.urlparams) { params = this.urlparams; delete params["url"]; }
		this.loadData(this.itemsurl,params);
	}
});
Ext.reg('dyncontainer',Ext.ux.DynContainer);




Ext.ux.AutoPanel = Ext.extend(Ext.Panel, {

	// Set the timeout to match the Ajax default:
	timeout: Ext.Ajax.timeout,

	setTitle: function() {
		Ext.ux.AutoPanel.superclass.setTitle.apply(this,arguments);
		
		// If our owner is the RapidApp 'main-load-target' TabPanel, this will
		// update the browser title
		if(this.ownerCt && Ext.isFunction(this.ownerCt.applyTabTitle)) {
			this.ownerCt.applyTabTitle();
		}
	},
	
	// Override Ext.Component.getId() auto id generation
	getId : function(){
		return this.id || (this.id = 'ap-' + (++Ext.Component.AUTO_ID));
	},
	
	cmpListeners: null,
	cmpConfig: {},
	update_cmpConfig: null,
	
	// Save the ID of the AutoPanel in the Updater object for referencing if
	// an exception (X-RapidApp-Exception) occurs during content load:
	doAutoLoad: function() {
		var u = this.body.getUpdater();
		
		// -- Set the 'Updater' timeout: (note conversion from millisecs to secs)
		u.timeout = (this.timeout)/1000;
		
		//New: allow custom timeout to be set via autoLoad param:
		if(Ext.isObject(this.autoLoad) && this.autoLoad.timeout) {
			u.timeout = (this.autoLoad.timeout)/1000;
		}
		// --
		
		// -----  AutoPanel failure handler  -----
		u.on('failure',function(el,response) {
			// --- RapidApp Exceptions are handled in global Ajax handlers:
			if(
				response && Ext.isFunction(response.getResponseHeader) &&
				response.getResponseHeader('X-RapidApp-Exception')
			) { return; }
			// ---
			
      var retry_text = [
       'Please try again later.&nbsp;&nbsp;',
       '<div style=height:20px;"></div>',
       '<span style="font-size:.7em;">',
       '<i>If you continue to receive this message, please contact your ',
       'System Administrator.</i></span>',
       '<div class="retry-foot">',
        '<center>',
          '<span',
            'class="with-icon ra-icon-refresh-24x24 ra-autopanel-reloader"',
            'style="font-size:1.5em;display:inline;vertical-align:baseline;padding-left:40px;"',
          '>Try Again</span>',
        '</center>',
       '</div>'
      ].join(' ');
			
			var title = 'Load Request Failed:';
			var msg = '<div style="padding:10px;font-size:1.3em;color:navy;">&nbsp;&nbsp;' +
			 response.statusText + 
			 '&nbsp;</div>' +
			 '<br>' + retry_text;
			var opt = { 
				tabTitle: '<span style="color:gray;">(load failed)</span>',
				tabIconCls: 'ra-icon-warning' 
			};
			
			// All-purpose timeout message:
			if(response.isTimeout) {
				opt.tabTitle = '<span style="color:gray;">(timed out)</span>';
				title = 'Load Request Timeout';
				msg = 'The page/content load request timed out.<br><br>Possible causes:<br>' +
				 '<ol style="list-style:circle inside;padding:20px;font-size:.8em;color:navy;">' +
				 '<li>Connection problem. (check to make sure you can access other sites)</li>' +
				 '<li>The server may be responding slowly due to an unusually high load.</li>' +
				 '<li>The system may be temporarily down for maintentence.</li>' +
				 '</ol>' + retry_text;
			}
			
			return this.setErrorBody(title,msg,opt);
		},this);
		// -----   ***   -----
		
		u.AutoPanelId = this.getId();
		Ext.ux.AutoPanel.superclass.doAutoLoad.call(this);
	},
	
  initComponent: function() {
    
    // -- Make sure no highlighting can happen during load (this prevents highlight
    //    bugs that can happen if we double-clicked something to spawn this panel)
    var thisEl;
    this.on('render',function(){
      thisEl = this.getEl();
      thisEl.addClass('ra-ap-body');
      thisEl.addClass('no-text-select');
      
      this.on('resize',this.setSafesizeClasses,this);
      this.setSafesizeClasses();
      
      // Listen for clicks on custom 'ra-autopanel-reloader' elements
      // to fire reload of the panel. This provides inline access to
      // this function within the html/content of the panel. 
      // (Added for Github Issue #24)
      thisEl.on('click',function(e,t,o) {
        var target = e.getTarget(null,null,true);
        if(target && target.hasClass('ra-autopanel-reloader')) {
        // in the case of nested AutoPanels, don't allow this event to
        // bubble up higher:
        e.stopEvent(); 
        this.reload();
        }
      }, this);
      
		},this);
		// Allowing highlighting within the panel once loading is complete:
		this.on('afterlayout',function(){
			thisEl = this.getEl();
			thisEl.removeClass('no-text-select');
		},this);
		// --

    var container = this;
    this.renderer = {
      disableCaching: true,
      render: function(el, response, updater, callback) {
        if (!updater.isUpdating() && el.dom) {
          
          var conf, content_type = response.getResponseHeader('Content-Type');
          var cont_parts = content_type.split(';');
          
          // Expected content-type returned by a RapidApp module:
          if(cont_parts[0] == 'text/javascript') {
            conf = Ext.decode(response.responseText);
          }
          else {
            var html, title, icon = 'ra-icon-document', 
              style = "font-weight:lighter;font-family:arial;";
            if (cont_parts[0] == 'text/html') {
              icon = 'ra-icon-page-white-world';
              html = response.responseText;
              
              // --- Support special syntax to parse tab title/icon/style
              var div = document.createElement('div');
              var El = new Ext.Element(div);
              El.createChild({
                tag: 'div',
                html: '<div style="padding:5px;">' + html + '</div>'
              });
              var titleEl = El.child('title');
              if(titleEl) {
                title = titleEl.dom.innerHTML || '';
                title.replace('^\\s+',''); // strip leading whitespace
                title.replace('\\s+$',''); // strip trailing whitespace
                icon = titleEl.getAttribute('class') || icon;
                style = titleEl.getAttribute('style') || style;
              }
             
              // ---
            }
            else if (cont_parts[0] == 'text/plain') {
              icon = 'ra-icon-page-white-text';
              html = Ext.util.Format.nl2br(Ext.util.Format.htmlEncode(response.responseText));
            }
            else {
              icon: 'ra-icon-page-white';
              html = '<b>Warning, Unknown Content-Type: ' + content_type + 
                '</b><br><br><pre>' + response.responseText + '</pre>';
            }
            
            if(!title || title == '') {
              title = cont_parts[0];
              var size = response.getResponseHeader('Content-Length');
              if(size) { title = title + ' [' + Ext.util.Format.fileSize(size) + ']'; }
            }
            
            conf = {
              xtype: 'panel',
              autoScroll: true,
              tabTitle: '<span style="' + style + '">' + title + '</span>',
              tabIconCls: icon,
              html: '<div style="padding:5px;">' + html + '</div>'
            };
          }
          
          Ext.apply(conf,container.cmpConfig);
            
          // new: 'update_cmpConfig' - same thing as cmpConfig except it is a
          // function-based api which allows updating the config based on 
          // the existing config instead of blindly like cmpConfig does
          if(Ext.isFunction(container.update_cmpConfig)) {
            container.update_cmpConfig(conf);
          }
          
          if(container.cmpListeners) {
            conf.initComponent = function() {
              this.on(container.cmpListeners);
              this.constructor.prototype.initComponent.call(this);
            };
          }
          
          // ------------------------------------
          // TODO/FIXME: new feature - deduplicate/refactor/merge with above -
          //  Allow regular JSON configs to also tap into the tab title/icon/style
          //  via parsing html content with special param 'autopanel_parse_title'
          // UPDATE: this new option now takes precidence over 'tabTitle', which is
          // now different than above
          if(conf.autopanel_parse_title && conf.html) {
            var div = document.createElement('div');
            var El = new Ext.Element(div);
            El.createChild({
              tag: 'div',
              html: conf.html
            });
            var titleEl = El.child('title');
            if(titleEl) {
              var style = titleEl.getAttribute('style');
              var title = titleEl.dom.innerHTML || '';
              title.replace('^\\s+',''); // strip leading whitespace
              title.replace('\\s+$',''); // strip trailing whitespace
              title = title || conf.tabTitle;
              title = style ? '<span style="' + style + '">' + title + '</span>' : title;
              conf.tabTitle = title || conf.tabTitle;
              conf.tabIconCls = titleEl.getAttribute('class') || conf.tabIconCls || 'ra-icon-page-white-world';
            }
          }
          // ------------------------------------
          
          // NEW: optional override option to disable any tab title/icon 
          // configured in returned page
          if(container.autopanel_ignore_tabtitle) {
            if(conf.tabTitle) { delete conf.tabTitle; }
            if(conf.tabIconCls) { delete conf.tabIconCls; }
            if(conf.tabTitleCls) { delete conf.tabTitleCls; }
          }
          
          // New: If this is html content (i.e. not an Ext container/panel)
          // set the default body class to 'ra-scoped-reset' to escape from the
          // global ExtJS CSS which does not have useful defaults for this case
          if(conf.html && !conf.bodyCssClass) {
            conf.bodyCssClass = 'ra-scoped-reset';
          }
          
          // just for good measure, stop any existing auto refresh:
          updater.stopAutoRefresh();
          
          container.setBodyConf.call(container,conf,el);
          
          // autopanel_refresh_interval can be set from either the inner
          // dynamic panel, or hard-coded on the autopanel container itself:
          var refresh_interval = 
            container.autopanel_refresh_interval ||
            conf.autopanel_refresh_interval;
          
          if(refresh_interval) {
            updater.startAutoRefresh(
              refresh_interval,
              container.autoLoad
            );
          }
          
          // This is legacy and should probably be removed:
          if (conf.rendered_eval) { eval(conf.rendered_eval); }
        }
      }
    };

    Ext.ux.AutoPanel.superclass.initComponent.call(this);
  },

  setBodyConf: function(conf,thisEl) {
    thisEl = thisEl || this.getEl();
    if(this.items.getCount() > 0) { this.removeAll(true); }
    
    // Clear the existing innerHTML (deletes the loading-indicator)
    // Previously, the loading-indicator was just hidden off the bottom
    // of the panel, but certain situations could make it show back up,
    // such as when the browser tries to scroll a label into view (as
    // described in Github Issue #46 which this change was added for).
    thisEl.dom.innerHTML = '';
    
    this.insert(0,conf);
    this.doLayout();
  },

  setErrorBody: function(title,msg,opt) {
    opt = opt || {};
    opt = Ext.apply({
      tabTitle: 'Load Failed',
      tabIconCls: 'ra-icon-cancel',
      html: [
        '<div class="ra-autopanel-error">',
          '<div class="ra-exception-heading">',
            title,
            '<span style="padding-left:20px;">',
              '<a class="with-icon ra-icon-refresh ra-autopanel-reloader">',
                '<span class="ra-autopanel-reloader">',
                  'Reload',
                '</span>',
              '</a>',
            '</span>',
          '</div>',
          '<div class="msg">',msg,'</div>',
        '</div>'
      ].join('')
    },opt);
    
    opt.bodyConf = opt.bodyConf || {
      layout: 'fit',
      autoScroll: true,
      frame: true,
      xtype: 'panel',
      html: opt.html
    };
    
    if(!this.autopanel_ignore_tabtitle) {
      this.setTitle(opt.tabTitle);
      this.setIconClass(opt.tabIconCls);
    }
    this.setBodyConf(opt.bodyConf,this.getEl());
  },
  
  reload: function() {
    this.load(this.autoLoad);
  },
  
  
  // This method sets 5 informational CSS classes on the body element
  // pertaining to the current size of the element within the browser.
  // There is no active RapidApp code that takes any action based on
  // the presence of these classes, however, it is available to
  // user-defined CSS/HTML. Each class is 1 of a predefined set of possible
  // values defining the "safesize" that inner content can be set to in order
  // to be visible without scrolling. These are for width alone, height
  // alone, and heightXwidth(ws), each of which might be useful in different
  // ways for CSS rules. For example, 'ra-safesize-w640' means that content
  // up to at least 640 pixels wide will be viewable without scrolling.
  // On the other hand, 'ra-safesize-800x600' means that at least that size
  // **sqaure** (i.e. 2-dimensions instead of 1) will be visible. CSS rules
  // can then take action to adjust inner content accordingly, such as
  // making sure a video in an iFrame will always display w/o scrolling
  //
  // There is also a 5th more general class set to one of:
  //   * ra-safesize-small
  //   * ra-safesize-medium
  //   * ra-safesize-large
  //
  // These are provided to limit the number of rules required to cover the
  // entire spectrum of sizes w/o gaps. They can be used alone, or in combination with 
  // the more-specific resolution values to zero in on the size at one end
  // of the size spectrum and not the other (for example, custom CSS could
  // be set for several different small styles and single rules for medium/large)
  setSafesizeClasses: function() {
    var El = this.getEl();
    var width = El.getWidth() + 4;
    var height = El.getHeight() + 4;
    
    // 4x3
    var xWidth = parseInt(height/0.75);
    if(xWidth > width) { xWidth = width; }
    
    //16x9
    var xwWidth = parseInt(height/0.5625);
    if(xwWidth > width) { xwWidth = width; }
    
    var wClass, hClass, xClass, xwClass, smlClass;
    
    wClass = 'ra-safesize-w0';
    if(width > 100)  { wClass = 'ra-safesize-w100'; }
    if(width > 320)  { wClass = 'ra-safesize-w320'; }
    if(width > 480)  { wClass = 'ra-safesize-w480'; }
    if(width > 640)  { wClass = 'ra-safesize-w640'; }
    if(width > 800)  { wClass = 'ra-safesize-w800'; }
    if(width > 1024) { wClass = 'ra-safesize-w1024'; }
    if(width > 1400) { wClass = 'ra-safesize-w1400'; }
    
    hClass = 'ra-safesize-h0';
    if(height > 50)  { hClass = 'ra-safesize-h50'; }
    if(height > 120) { hClass = 'ra-safesize-h120'; }
    if(height > 240) { hClass = 'ra-safesize-h240'; }
    if(height > 360) { hClass = 'ra-safesize-h360'; }
    if(height > 480) { hClass = 'ra-safesize-h480'; }
    if(height > 600) { hClass = 'ra-safesize-h600'; }
    if(height > 768) { hClass = 'ra-safesize-h768'; }
    if(height > 1050) { hClass = 'ra-safesize-h768'; }
    
    xClass = 'ra-safesize-0x0';
    if(xWidth > 200)  { xClass = 'ra-safesize-200x150'; }
    if(xWidth > 320)  { xClass = 'ra-safesize-320x240'; }
    if(xWidth > 480)  { xClass = 'ra-safesize-480x360'; }
    if(xWidth > 640)  { xClass = 'ra-safesize-640x480'; }
    if(xWidth > 800)  { xClass = 'ra-safesize-800x600'; }
    if(xWidth > 1024) { xClass = 'ra-safesize-1024x768'; }
    if(xWidth > 1400) { xClass = 'ra-safesize-1400x1050'; }
    
    xwClass = 'ra-safesize-0x0ws';
    if(xwWidth > 320)  { xwClass = 'ra-safesize-320x200ws'; }
    if(xwWidth > 480)  { xwClass = 'ra-safesize-480x270ws'; }
    if(xwWidth > 640)  { xwClass = 'ra-safesize-640x360ws'; }
    if(xwWidth > 800)  { xwClass = 'ra-safesize-800x450ws'; }
    if(xwWidth > 1024) { xwClass = 'ra-safesize-1024x576ws'; }
    if(xwWidth > 1280) { xwClass = 'ra-safesize-1280x720ws'; }
    if(xwWidth > 1920) { xwClass = 'ra-safesize-1920x1080ws'; }
    
    // Alternate broader small/medium/large
    smlClass = 'ra-safesize-small';
    if(xWidth > 480)  { smlClass = 'ra-safesize-medium'; }
    if(xWidth > 800)  { smlClass = 'ra-safesize-large'; }
    
    if(this.current_safesize_Classes) {
      El.removeClass(this.current_safesize_Classes);
    }
    
    this.current_safesize_Classes = [wClass,hClass,xClass,xwClass,smlClass];
    El.addClass(this.current_safesize_Classes);
  }
  
});
Ext.reg('autopanel',Ext.ux.AutoPanel);



Ext.ux.DynGridPanel = Ext.extend(Ext.grid.GridPanel, {

	border: false,
	initComponent: function() {

		var store = new Ext.data.JsonStore(this.store_config);

		var Toolbar = {
			xtype : 'paging',
			store : store,
			displayInfo : true,
			prependButtons: true
		};
		if(this.pageSize) { Toolbar['pageSize'] = parseFloat(this.pageSize); }
		if(this.paging_bbar) { Toolbar['items'] = this.paging_bbar; }


		// --------- this doesn't work:
		//var new_column_model = [];
		//for ( var i in this.column_model ) {
		//	if (!this.column_model[i].exclude) {
		//		new_column_model.push(this.column_model[i]);
		//	}
		//}
		//this.column_model = new_column_model;


		// ----- MultiFilters: ----- //
		if (this.use_multifilters) {
			if(!this.plugins){ this.plugins = []; }
			this.plugins.push(new Ext.ux.MultiFilter.Plugin);
		}
		// ------------------------- //


		// ----- RowExpander ------ //
		if (this.expander_template) {
			var expander_config = {};
			expander_config.tpl = new Ext.Template(this.expander_template);
			if (this.getRowClass_eval) { expander_config.getRowClass_eval = this.getRowClass_eval; }
			var expander = new Ext.ux.grid.RowExpanderEX(expander_config);
			this.column_model.unshift(expander);
			if(!this.plugins){ this.plugins = []; }
			this.plugins.push(expander);
			this.expander = expander;
		}
		// ----------------------- //






		// ----- RowActions ------ //
		var thisG = this;
		if (this.rowactions && this.rowactions.actions) {
			var new_actions = [];
			for (var i in thisG.rowactions.actions) {
				var action_config = thisG.rowactions.actions[i];
				if(this.rowactions.callback_eval) {
					action_config.callback = function(grid, record, action, groupId) { eval(thisG.rowactions.callback_eval); }
				}
				new_actions.push(action_config);
			}
			this.rowactions.actions = new_actions;
			var action = new Ext.ux.grid.RowActions(this.rowactions);
			if(!this.plugins){ this.plugins = []; }
			this.plugins.push(action);
			this.column_model.push(action);
		}
		// ----------------------- //



		// ---------------------------- //
		// ------ Grid Search --------- //
		if (this.gridsearch) {

			var grid_search_cnf = {
				iconCls:'ra-icon-zoom',
				//,readonlyIndexes:['note']
				//,disableIndexes:['pctChange']
				//minChars:3, 		// characters to type before the request is made. If undefined (the default)
										// the trigger field shows magnifier icon and you need to click it or press enter for search to start.
				autoFocus:false,
				mode: 'local', // local or remote
				width: 300,
				position: 'top'
				//,menuStyle:'radio'
			};

			if (this.gridsearch_remote) { grid_search_cnf['mode'] = 'remote'; }

			if(!this.plugins){ this.plugins = []; }
			this.plugins.push(new Ext.ux.grid.Search(grid_search_cnf));
		}
		// ---------------------------- //


	 // ------ Grid Filter --------- //
		//if(this.gridfilter) {

			var grid_filter_cnf = {
				encode: true, // json encode the filter query
				local: true   // defaults to false (remote filtering)
			}

			if (this.gridfilter_remote) { grid_filter_cnf['local'] = false; }


			if(this.init_state) {
				grid_filter_cnf['init_state'] = this.init_state;
				//{
				//	filters: this.init_filters
				//};


				//console.dir(this.init_state);
			}

			var GridFilters = new Ext.ux.grid.GridFilters(grid_filter_cnf);

			if(!this.plugins){ this.plugins = []; }
			this.plugins.push(GridFilters);
		//}
	// ---------------------------- //

		var sm = new Ext.grid.RowSelectionModel();

		// ------- SelectionModel -------- //
		if (this.row_checkboxes) {
			sm = new Ext.grid.CheckboxSelectionModel();
			this.column_model.unshift(sm);
		}
		// ------------------------------- //

		var config = {
			stateful: false,
			enableColumnMove: true,
			store: store,
			columns: this.column_model,
			selModel: sm,
			layout: 'fit',
			id: this.gridid,
			loadMask: true,
			storeReload: function(grid) {
				grid.store.reload();
			},

			// ------- http://extjs.com/forum/showthread.php?p=97676#post97676
			autoSizeColumns: function() {
				if (this.colModel) {

					this.colModel.suspendEvents();
					for (var i = 0; i < this.colModel.getColumnCount(); i++) {
						this.autoSizeColumn(i);
					}
					this.colModel.resumeEvents();
					this.view.refresh(true);
					this.store.removeListener('load',this.autoSizeColumns,this);

				}
			},
			autoSizeColumn: function(c) {
				var colid = this.colModel.getColumnId(c);
				var column = this.colModel.getColumnById(colid);
				var col = this.view.el.select("td.x-grid3-td-" + colid + " div:first-child");
				if (col) {

					var add = 6;
					var w = col.getTextWidth() + Ext.get(col.elements[0]).getFrameWidth('lr') + add;

					if (this.MaxColWidth && w > this.MaxColWidth) { w =  this.MaxColWidth; }
					if (column.width && w < column.width) { w = column.width; }

					this.colModel.setColumnWidth(c, w);
					return w;
				}
			}
			// ------------------------
		};

		if (Toolbar) { config['bbar'] = Toolbar; }



		Ext.apply(this, Ext.apply(this.initialConfig, config));
		Ext.ux.DynGridPanel.superclass.initComponent.apply(this, arguments);
	},

	onRender: function() {

		//var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Loading data, please wait..."});
		//myMask.show();


		// ------- Remote Columns -------- //
		var thisGrid = this;
		if (this.remote_columns) {
			this.store.on('beforeload',function(Store,opts) {
				var columns = thisGrid.getColumnModel().getColumnsBy(function(c){
					if(c.hidden || c.dataIndex == "" || c.dataIndex == "icon") { return false; }
					return true;
				});
				var colIndexes = [];
				for (i in columns) {
					colIndexes.push(columns[i].dataIndex);
				}
				//Store.setBaseParam("columns",Ext.encode(colIndexes));
				Store.baseParams["columns"] = Ext.encode(colIndexes);
			});
			this.getColumnModel().on('hiddenchange',function(colmodel) {

				// For some reason I don't understand, reloading the store directly
				// does not make it see the new non-hidden column names, but calling
				// the refresh function on the paging toolbar does:
				var ptbar = thisGrid.getBottomToolbar();
				ptbar.doRefresh();
				//var Store = thisGrid.getStore();
				//Store.reload();
			});
		}
		// ------------------------------- //


		var load_parms = null;
		if (this.pageSize) {
			load_parms = {
				params: {
					start: 0,
					limit: parseFloat(this.pageSize)
				}
			};
		}

		this.store.load(load_parms);

		Ext.ux.DynGridPanel.superclass.onRender.apply(this, arguments);

		var thisC = this;

		function StartReloadInterval(mystore,i) {
			function ReloadStore() { mystore.reload(); }
			setInterval(ReloadStore,i);
		}
		if (this.reload_interval > 0) {
			StartReloadInterval(thisC.store,thisC.reload_interval);
		}

		if (this.UseAutoSizeColumns) {
			//this.store.on('load',thisC.autoSizeColumns,thisC);
			this.store.on('load',function(grid) {
				var sizeFunc = function(){thisC.autoSizeColumns();}
				sizeFunc();
			});
		}



		// ---- this is old:
		/*
		this.on('celldblclick',function(grid, rowIndex, columnIndex, e) {

			var viewPan = Ext.getCmp('viewingPanel');
			viewPan.expand();
			viewPan.doLayout();
			//alert(data);
		});
		*/
		// -----------------

		this.on('cellclick',function(grid, rowIndex, columnIndex, e) {
			var record = grid.getStore().getAt(rowIndex);  // Get the Record
			var col_model = grid.getColumnModel();
			var fieldName = col_model.getDataIndex(columnIndex); // Get field name

			if (this.expander && this.expander_click_rows) {
				if (this.expander_click_rows[columnIndex]) {
					this.expander.toggleRow(rowIndex);
				}
			}

			//var colid = col_model.getColumnId(fieldName);
			//var column = col_model.getColumnById(colid);

		});


		// ------ Cell Doubleclick -------- //
		if(this.celldblclick_eval) {
			//alert(thisC.rowbodydblclick_eval);
			//this.on('rowbodydblclick', function(grid, rowIndex, e) {
			this.on('celldblclick', function(grid, rowIndex, columnIndex, e) {
				var record = grid.getStore().getAt(rowIndex);
				var fieldName = grid.getColumnModel().getDataIndex(columnIndex);
				eval(this.celldblclick_eval);
			});
		}
		// -------------------------------- //

		//window.busy = false;

		//myMask.hide();

	},
	getFilters: function(grid) {
		for (i in grid.plugins) {
			if (grid.plugins[i]['filters']) {
				return grid.plugins[i];
			}
		}
		return null;
	}
});
Ext.reg('dyngrid',Ext.ux.DynGridPanel);



//var orig_gf_init = Ext.ux.grid.GridFilters.prototype.init;




/*
Ext.override(Ext.ux.GridFilters, {
	initComponent: function() {

		var config = {
			getState: function () {
				var filters = {};
				this.filters.each(function (filter) {
					if (filter.active) {
						filters[filter.dataIndex] = filter.getValue();
					}
				});
				return filters;
			}
		};
		Ext.apply(this, Ext.apply(this.initialConfig, config));
		Ext.ux.GridFilters.superclass.initComponent.apply(this, arguments);
	}
});
*/





Ext.ux.DButton = Ext.extend(Ext.Button, {

	initComponent: function() {

		if (this.handler_func) {
			var config = {
				handler: function(btn) { eval(this.handler_func); }
			};
			Ext.apply(this, Ext.apply(this.initialConfig, config));
		}
		Ext.ux.DButton.superclass.initComponent.apply(this, arguments);
	},
	afterRender: function() {
		if (this.submitFormOnEnter) {
			var formPanel = this.findParentByType('form');
			if (!formPanel) {
				formPanel = this.findParentByType('submitform');
			}
			new Ext.KeyMap(formPanel.el, {
				key: Ext.EventObject.ENTER,
				shift: false,
				alt: false,
				fn: function(keyCode, e){
						if(e.target.type === 'textarea' && !e.ctrlKey) {
							return true;
						}
						this.el.select('button').item(0).dom.click();
						return false;
				},
				scope: this
			});
		}
		Ext.ux.DButton.superclass.afterRender.apply(this, arguments);
	}
});
Ext.reg('dbutton',Ext.ux.DButton);


Ext.ux.TreePanelExt = Ext.extend(Ext.tree.TreePanel, {

	onRender: function() {
		if (this.click_handler_func) {
			this.on('click',function(node,e) { if (node) { eval(this.click_handler_func); }});
		}

		Ext.ux.TreePanelExt.superclass.onRender.apply(this, arguments);
	},
	afterRender: function() {
		Ext.ux.TreePanelExt.superclass.afterRender.apply(this, arguments);

		if (this.expand) { this.expandAll(); }

		if (this.afterRender_eval) {

			eval(this.afterRender_eval);

			/*
			var eval_str = this.afterRender_eval;
			var task = new Ext.util.DelayedTask(function() { eval(eval_str); });
			task.delay(500);
			*/

		}
	}
});
Ext.reg('treepanelext',Ext.ux.TreePanelExt );


// learned about this from: http://www.diloc.de/blog/2008/03/05/how-to-submit-ext-forms-the-right-way/
Ext.ux.JSONSubmitAction = function(form, options){
	 Ext.ux.JSONSubmitAction.superclass.constructor.call(this, form, options);
};
Ext.extend(Ext.ux.JSONSubmitAction, Ext.form.Action.Submit, {

	type : 'jsonsubmit',

	run : function(){
		  var o = this.options,
				method = this.getMethod(),
				isGet = method == 'GET';
		  if(o.clientValidation === false || this.form.isValid()){
				if (o.submitEmptyText === false) {
					 var fields = this.form.items,
						  emptyFields = [];
					 fields.each(function(f) {
						  if (f.el.getValue() == f.emptyText) {
								emptyFields.push(f);
								f.el.dom.value = "";
						  }
					 });
				}

				var orig_p = this.form.orig_params;
				var new_p = this.form.getFieldValues();

				var ajax_params = o.base_params ? o.base_params : {};
				ajax_params['json_params'] = Ext.util.JSON.encode(new_p);
				if (this.form.orig_params) {
					ajax_params['orig_params'] = Ext.util.JSON.encode(orig_p);
				}






				//Ext.getCmp('dataview').getStore().reload();

				//var cmp = this.form.findField('dataview');
				//alert(cmp.getXtype());

				//this.cascade(function (cmp) {
				//	try { if (cmp.getXtype()) { alert(cmp.getXtype()); } } catch(err) {}
				//});


				Ext.Ajax.request(Ext.apply(this.createCallback(o), {
					 //form:this.form.el.dom,  <--- need to remove this line to prevent the form items from being submitted
					 url:this.getUrl(isGet),
					 method: method,
					 headers: o.headers,
					 //params:!isGet ? this.getParams() : null,
					 params: ajax_params,
					 isUpload: this.form.fileUpload
				}));
				if (o.submitEmptyText === false) {
					 Ext.each(emptyFields, function(f) {
						  if (f.applyEmptyText) {
								f.applyEmptyText();
						  }
					 });
				}
		  }else if (o.clientValidation !== false){ // client validation failed
				this.failureType = Ext.form.Action.CLIENT_INVALID;
				this.form.afterAction(this, false);
		  }
	 }
});
//add our action to the registry of known actions
Ext.form.Action.ACTION_TYPES['jsonsubmit'] = Ext.ux.JSONSubmitAction;


Ext.ux.SubmitFormPanel = Ext.extend(Ext.form.FormPanel, {

	initComponent: function() {




		var thisC = this;

		var config = {
			resultProcessor: function(form, action) {
				thisC.el.unmask();
				if (action.result.success) {
					if (thisC.show_result) { Ext.MessageBox.alert('Success',action.result.msg); }
					if (thisC.onSuccess_eval) {
						eval(thisC.onSuccess_eval);





						//alert(this.getComponent('itemdataview').getXType());


						//var store = thisC.getComponent('itemdataview').store;
						//store.reload;

						//var store = Ext.getCmp('mydataview').store;
						//store.reload;

						//alert(Ext.util.JSON.encode(action.params));
						//Ext.Msg.alert('blah',Ext.util.JSON.encode(thisC.base_params));

						//Ext.StoreMgr.each( function(store) {
						//	for ( var i in thisC.base_params ) {
						//		store.setBaseParam(i, thisC.base_params[i]);
						//	}
						//	store.reload();
						//});
					}
				}
				else {
					if (thisC.onFail_eval) { eval(thisC.onFail_eval); }
					if (thisC.show_result) { Ext.MessageBox.alert('Failure',action.result.msg); }
				}
			},

			submitProcessor: function() {

				var do_action = this.do_action ? this.do_action : 'submit';
				var base_params = this.base_params ? this.base_params : {};





				//Ext.StoreMgr.each( function(store) {
				//	for ( var i in base_params ) {
				//		store.setBaseParam(i, base_params[i]);
				//	}
				//	store.reload();
				//});

				this.el.mask('Please wait','x-mask-loading');
				//this.getForm().submit({
				//this.getForm().doAction('jsonsubmit',{
				this.getForm().doAction(do_action,{
					url: this.url,
					base_params: base_params,
					nocache: true,
					success: this.resultProcessor,
					failure: this.resultProcessor
				});
			}
		};

		Ext.apply(this, Ext.apply(this.initialConfig, config));
		Ext.ux.SubmitFormPanel.superclass.initComponent.apply(this, arguments);
	},

	afterRender: function() {

		//if (this.map_enter_submit) {
		//	var map = new Ext.KeyMap(document, {
		//		key: 13,
		//		//scope: this,
		//		fn: function() { alert('enter!'); }
		//	});
		//}




		this.on('actioncomplete', function(form,action) {
			if(action.type == 'load') {
			// save the orig params so they are available later in the jsonsubmit action
			form.orig_params = form.getFieldValues();

				//find any stores within this container and reload them:
				this.cascade(function(cmp) {
					var xtype = cmp.getXType();
					if(xtype == "dyngrid" || xtype == "dataview") {
						Ext.log(cmp.getXType());
						try { cmp.getStore().reload(); } catch(err) { Ext.log(err); }
					}
				});
			}
		});



/*
		this.on('actioncomplete', function(form,action) {
			if(action.type == 'load') {
				form.orig_params = form.getFieldValues();



				var store = this.getComponent('itemdataview').getStore();
				//var store = Ext.getCmp('mydataview').getStore();
				//alert(Ext.util.JSON.encode(store.baseParams));
				var new_p = this.getForm().getFieldValues();
				for ( i in store.baseParams ) {
					if (new_p[i]) { store.setBaseParam(i,new_p[i]); }
				}
				//alert(Ext.util.JSON.encode(store.baseParams));
				store.reload();


			}
		});
*/



/*
		this.on('activate', function(form,action) {
			if (this.action_load) {
				var action_load = this.action_load;
				action_load['waitTitle'] = 'Loading';
				action_load['waitMsg'] = 'Loading data';
				var form = this.getForm();
				form.load(action_load);
			}

		});
*/


		// Load the form data: //
		if (this.action_load) {
			var action_load = this.action_load;
			action_load['waitTitle'] = 'Loading';
			action_load['waitMsg'] = 'Loading data';
			var form = this.getForm();
			form.load(action_load);
		}


		if (this.focus_field_id) {
			var field = Ext.getCmp(this.focus_field_id);
			if (field) { field.focus('',10); }
		}
		Ext.ux.SubmitFormPanel.superclass.afterRender.apply(this, arguments);
	}
});
Ext.reg('submitform',Ext.ux.SubmitFormPanel );















//Ext.reg('categoryaxis',Ext.chart.CategoryAxis );
//Ext.reg('numericaxis',Ext.chart.NumericAxis );

//Ext.QuickTips = function(){};

//Ext.override(Ext.QuickTips, function() {});



//Ext.override(Ext.ux.Printer.BaseRenderer, { stylesheetPath: '/static/js/Ext.ux.Printer/print.css' });

/*
 * Prints the contents of an Ext.Panel
*/
// Ext.ux.Printer.PanelRenderer = Ext.extend(Ext.ux.Printer.BaseRenderer, {

/*
  * Generates the HTML fragment that will be rendered inside the <html> element of the printing window
 */
//	generateBody: function(panel) {
//		return String.format("<div class='x-panel-print'>{0}</div>", panel.body.dom.innerHTML);
//	}
//});

//Ext.ux.Printer.registerRenderer("panel", Ext.ux.Printer.PanelRenderer);





































Ext.ux.FloatClear = Ext.extend(Ext.Component, {
	cls: 'x-clear'
});
Ext.reg('float-clear', Ext.ux.FloatClear);

Ext.ux.FloatingFormLayout = Ext.extend(Ext.layout.FormLayout, {
	getLabelStyle: function(s, field) {
		var labelStyle = this.labelStyle;
		if (this.labelAlign !== 'top') {
			if (field.labelWidth) {
				labelStyle = 'width:' + field.labelWidth + 'px;';
			}
		}
		var ls = '', items = [labelStyle, s];
		for (var i = 0, len = items.length; i < len; ++i) {
			if (items[i]) {
				ls += items[i];
				if (ls.substr(-1, 1) != ';') {
					ls += ';';
				}
			}
		}
		return ls;
	},

	getElementStyle: function(field) {
		if (this.labelAlign === 'top' || !field.labelWidth) {
			return this.elementStyle;
		} else {
			var pad = Ext.isNumber(this.labelPad) ? this.labelPad : 5;
			return 'padding-left:' + (field.labelWidth + pad) + 'px';
		}
	},

	getTemplateArgs: function(field) {
		var noLabelSep = !field.fieldLabel || field.hideLabel;

		return {
			id: field.id,
			label: field.fieldLabel,
			itemCls: (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : ''),
			clearCls: field.clearCls || 'x-form-clear-left',
			labelStyle: this.getLabelStyle(field.labelStyle, field),
			elementStyle: this.getElementStyle(field) || '',
			labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator)
		};
	}
});
Ext.Container.LAYOUTS['floating-form'] = Ext.ux.FloatingFormLayout;

Ext.ux.FloatingFormPanel = Ext.extend(Ext.form.FormPanel, {
	cls: 'floating-form',
	layout: 'floating-form',
	lookupComponent: function(comp) {
		if (Ext.isString(comp)) {
			switch (comp) {
				case "|":
					comp = new Ext.ux.FloatClear();
					break;
			}
		}
		return Ext.ux.FloatingFormPanel.superclass.lookupComponent.call(this, comp);
	}
});
Ext.reg('floating-form', Ext.ux.FloatingFormPanel);


Ext.ns('Ext.ux');
Ext.ux.ComponentDataView = Ext.extend(Ext.DataView, {
	 defaultType: 'textfield',
	 initComponent : function(){
		  Ext.ux.ComponentDataView.superclass.initComponent.call(this);
		  this.components = [];
	 },
	 refresh : function(){
		  Ext.destroy(this.components);
		  this.components = [];
		  Ext.ux.ComponentDataView.superclass.refresh.call(this);
		  this.renderItems(0, this.store.getCount() - 1);
	 },
	 onUpdate : function(ds, record){
		  var index = ds.indexOf(record);
		  if(index > -1){
				this.destroyItems(index);
		  }
		  Ext.ux.ComponentDataView.superclass.onUpdate.apply(this, arguments);
		  if(index > -1){
				this.renderItems(index, index);
		  }
	 },
	 onAdd : function(ds, records, index){
		  var count = this.all.getCount();
		  Ext.ux.ComponentDataView.superclass.onAdd.apply(this, arguments);
		  if(count !== 0){
				this.renderItems(index, index + records.length - 1);
		  }
	 },
	 onRemove : function(ds, record, index){
		  this.destroyItems(index);
		  Ext.ux.ComponentDataView.superclass.onRemove.apply(this, arguments);
	 },
	 onDestroy : function(){
		  Ext.ux.ComponentDataView.onDestroy.call(this);
		  Ext.destroy(this.components);
		  this.components = [];
	 },
	 renderItems : function(startIndex, endIndex){
		  var ns = this.all.elements;
		  var args = [startIndex, 0];
		  for(var i = startIndex; i <= endIndex; i++){
				var r = args[args.length] = [];
				for(var items = this.items, j = 0, len = items.length, c; j < len; j++){
					 c = items[j].render ?
						  c = items[j].cloneConfig() :
						  Ext.create(items[j], this.defaultType);
					 r[j] = c;
					 if(c.renderTarget){
						  c.render(Ext.DomQuery.selectNode(c.renderTarget, ns[i]));
					 }else if(c.applyTarget){
						  c.applyToMarkup(Ext.DomQuery.selectNode(c.applyTarget, ns[i]));
					 }else{
						  c.render(ns[i]);
					 }
					 if(Ext.isFunction(c.setValue) && c.applyValue){
						  c.setValue(this.store.getAt(i).get(c.applyValue));
						  c.on('blur', function(f){
							this.store.getAt(this.index).data[this.dataIndex] = f.getValue();
						  }, {store: this.store, index: i, dataIndex: c.applyValue});
					 }
				}
		  }
		  this.components.splice.apply(this.components, args);
	 },
	 destroyItems : function(index){
		  Ext.destroy(this.components[index]);
		  this.components.splice(index, 1);
	 }
});
Ext.reg('compdataview', Ext.ux.ComponentDataView);



Ext.ux.ComponentListView = Ext.extend(Ext.ListView, {
	 defaultType: 'textfield',
	 initComponent : function(){
		  Ext.ux.ComponentListView.superclass.initComponent.call(this);
		  this.components = [];
	 },
	 refresh : function(){
		  Ext.destroy(this.components);
		  this.components = [];
		  Ext.ux.ComponentListView.superclass.refresh.apply(this, arguments);
		  this.renderItems(0, this.store.getCount() - 1);
	 },
	 onUpdate : function(ds, record){
		  var index = ds.indexOf(record);
		  if(index > -1){
				this.destroyItems(index);
		  }
		  Ext.ux.ComponentListView.superclass.onUpdate.apply(this, arguments);
		  if(index > -1){
				this.renderItems(index, index);
		  }
	 },
	 onAdd : function(ds, records, index){
		  var count = this.all.getCount();
		  Ext.ux.ComponentListView.superclass.onAdd.apply(this, arguments);
		  if(count !== 0){
				this.renderItems(index, index + records.length - 1);
		  }
	 },
	 onRemove : function(ds, record, index){
		  this.destroyItems(index);
		  Ext.ux.ComponentListView.superclass.onRemove.apply(this, arguments);
	 },
	 onDestroy : function(){
		  Ext.ux.ComponentDataView.onDestroy.call(this);
		  Ext.destroy(this.components);
		  this.components = [];
	 },
	 renderItems : function(startIndex, endIndex){
		  var ns = this.all.elements;
		  var args = [startIndex, 0];
		  for(var i = startIndex; i <= endIndex; i++){
				var r = args[args.length] = [];
				for(var columns = this.columns, j = 0, len = columns.length, c; j < len; j++){
					 var component = columns[j].component;
					 c = component.render ?
						  c = component.cloneConfig() :
						  Ext.create(component, this.defaultType);
					 r[j] = c;
					 var node = ns[i].getElementsByTagName('dt')[j].firstChild;
					 if(c.renderTarget){
						  c.render(Ext.DomQuery.selectNode(c.renderTarget, node));
					 }else if(c.applyTarget){
						  c.applyToMarkup(Ext.DomQuery.selectNode(c.applyTarget, node));
					 }else{
						  c.render(node);
					 }
					 if(c.applyValue === true){
						c.applyValue = columns[j].dataIndex;
					 }
					 if(Ext.isFunction(c.setValue) && c.applyValue){
						  c.setValue(this.store.getAt(i).get(c.applyValue));
						  c.on('blur', function(f){
							this.store.getAt(this.index).data[this.dataIndex] = f.getValue();
						  }, {store: this.store, index: i, dataIndex: c.applyValue});
					 }
				}
		  }
		  this.components.splice.apply(this.components, args);
	 },
	 destroyItems : function(index){
		  Ext.destroy(this.components[index]);
		  this.components.splice(index, 1);
	 }
});
Ext.reg('complistview', Ext.ux.ComponentListView);


Ext.override(Ext.ux.ComponentListView, {
	 onDestroy : function(){
		  Ext.ux.ComponentListView.superclass.onDestroy.call(this);
		  Ext.destroy(this.components);
		  this.components = [];
	 }
});

Ext.override(Ext.ux.ComponentDataView, {
	 onDestroy : function(){
		  Ext.ux.ComponentDataView.superclass.onDestroy.call(this);
		  Ext.destroy(this.components);
		  this.components = [];
	 }
});


Ext.ns('Ext.ux');
Ext.ux.TplTabPanel = Ext.extend(Ext.TabPanel, {
	 initComponent: function () {
		  //Ext.apply(this,{store:this.store});
		  Ext.ux.TplTabPanel.superclass.initComponent.apply(this, arguments);

		  var tb = this;
		  var itemArr = [];

		  var cnt = tb.store.getCount();

		  Ext.each(this.tabsTpl, function (j) {
				for (var i = 0; i < tb.store.getCount(); i++) {


					 var c = j.render ? c = j.cloneConfig() : Ext.ComponentMgr.create(j);


					 function myfn() {
						  Ext.apply(this, tb.store.getAt(i).get(this.applyValues));
					 }
					 c.cascade(myfn);
					 Ext.ComponentMgr.register(c);

					 tb.items.add(c.id, c);

				}
		  });

	 }
});
Ext.reg('tabtpl', Ext.ux.TplTabPanel);



//http://www.sencha.com/forum/showthread.php?77984-Field-help-text-plugin.
Ext.ux.FieldHelp = Ext.extend(Object, (function(){
	 function syncInputSize(w, h) {
		  this.el.setSize(w, h);
	 }

	 function afterFieldRender() {
		  if (!this.wrap) {
				this.wrap = this.el.wrap({cls: 'x-form-field-wrap'});
				this.positionEl = this.resizeEl = this.wrap;
				this.actionMode = 'wrap';
				this.onResize = this.onResize.createSequence(syncInputSize);
		  }
		  this.wrap[this.helpAlign == 'top' ? 'insertFirst' : 'createChild']({
				cls: 'x-form-helptext',
				html: this.helpText
		  });
	 }

	 return {
		  constructor: function(t, align) {
				this.helpText = t.text; // <-- changed from t to t.text (HV)
				this.align = align;
		  },

		  init: function(f) {
				f.helpAlign = this.align;
				f.helpText = this.helpText;
				f.afterRender = f.afterRender.createSequence(afterFieldRender);
		  }
	 };
})());
Ext.preg('fieldhelp',Ext.ux.FieldHelp);


/* 2011-01-28 by HV:
 Extended Saki's Ext.ux.form.DateTime to updateValue on 'select' and then 
 fire the new event 'updated'
*/
Ext.ns('Ext.ux.RapidApp.form');
Ext.ux.RapidApp.form.DateTime2 = Ext.extend(Ext.ux.form.DateTime ,{
	initComponent: function() {
		Ext.ux.RapidApp.form.DateTime2.superclass.initComponent.call(this);
		this.addEvents( 'updated' );
		this.on('change',this.updateValue,this);
		this.on('select',this.updateValue,this);
		this.relayEvents(this.df, ['change','select']);
		this.relayEvents(this.tf, ['change','select']);
		this.setMinMax();
	},
	
	setMinMax: function(newDate) {
		
		if (this.minValue) {
			var val = this.minValue;
			var dt = Date.parseDate(val, this.hiddenFormat);
			this.df.setMinValue(dt);
			
			if (newDate && newDate.getDayOfYear() != dt.getDayOfYear()) {
				this.setTimeFullRange();
			}
			else {
				this.tf.setMinValue(dt);
			}
		}
		if (this.maxValue) {
			var val = this.maxValue;
			var dt = Date.parseDate(val, this.hiddenFormat);
			this.df.setMaxValue(dt);
			
			if (newDate && newDate.getDayOfYear() != dt.getDayOfYear()) {
				this.setTimeFullRange();
			}
			else {
				this.tf.setMaxValue(dt);
			}
		}
	},
	
	setTimeFullRange: function() {
		var MaxDt = new Date();
		MaxDt.setHours(23);
		MaxDt.setMinutes(59);
		MaxDt.setSeconds(59);
		this.tf.setMaxValue(MaxDt);
		
		var MinDt = new Date();
		MinDt.setHours(0);
		MinDt.setMinutes(0);
		MinDt.setSeconds(0);
		this.tf.setMinValue(MinDt);
	},
	
	updateValue: function(cmp,newVal) {
		Ext.ux.RapidApp.form.DateTime2.superclass.updateValue.call(this);
		
		var newDate = null;
		if(newVal && newVal.getDayOfYear) { newDate = newVal; }
		
		this.setMinMax(newDate);
		this.fireEvent('updated',this);
	},
  
  // New: return formatted date string instead of Date object
  // this prevents the system seeing the value as changed when
  // it hasn't and producing a db update
  getValue: function() {
    var dt = this.dateValue ? new Date(this.dateValue) : null;
    return dt ? dt.format(this.hiddenFormat) : '';
  }
});
Ext.reg('xdatetime2', Ext.ux.RapidApp.form.DateTime2);


/*
 Creates a "tool" button just like the tools from "tools" in Ext.Panel
 Inspired by: http://www.sencha.com/forum/showthread.php?119956-use-x-tool-close-ra-icon-in-toolbar&highlight=tool+button
*/
Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.ClickBox = Ext.extend(Ext.BoxComponent, {

	cls: null,
	overCls: null,
	qtip: null,
	handler: function(){},
	scope: null,
	initComponent: function() {
		
		if(!this.scope) {
			this.scope = this;
		}
		
		this.autoEl = {};
		if(this.cls) { 
			this.autoEl.cls = this.cls;
		}
		if(this.qtip) { 
			this.autoEl['ext:qtip'] = this.qtip; 
		}
		
		Ext.ux.RapidApp.ClickBox.superclass.initComponent.call(this);
		
		this.on('afterrender',function(box) {
		 	var el = box.getEl();
			if(this.overCls) {
				el.addClassOnOver(this.overCls);
			}
			el.on('click', this.handler, this.scope, box);
		},this);
	}
});
Ext.reg('clickbox', Ext.ux.RapidApp.ClickBox);

Ext.ux.RapidApp.BoxToolBtn = Ext.extend(Ext.ux.RapidApp.ClickBox, {

	toolType: 'gear',

	initComponent: function() {
		
    var cls = this.cls;
		this.cls = 'x-tool x-tool-' + this.toolType;
    if(cls){ this.cls += ' ' + cls; }
		this.overCls = 'x-tool-' + this.toolType + '-over';
		if(this.toolQtip) { this.qtip = this.toolQtip; }
		
		Ext.ux.RapidApp.BoxToolBtn.superclass.initComponent.call(this);
	}
});
Ext.reg('boxtoolbtn', Ext.ux.RapidApp.BoxToolBtn);



Ext.ux.RapidApp.ComponentDataView = Ext.extend(Ext.ux.ComponentDataView,{
	initComponent: function() {
		Ext.each(this.items,function(item) {
			item.ownerCt = this;
		},this);
		Ext.ux.RapidApp.ComponentDataView.superclass.initComponent.call(this);
	}
});
Ext.reg('rcompdataview', Ext.ux.RapidApp.ComponentDataView);

Ext.ux.RapidApp.renderUtcDate= function(dateStr) {
	try {
		var dt= new Date(Date.parseDate(dateStr, "Y-m-d g:i:s"));
		var now= new Date();
		var utc= dt.getTime();
		dt.setTime(utc + Ext.ux.RapidApp.userPrefs.timezoneOffset*60*1000);
		var fmt= (now.getTime() - dt.getTime() > 1000*60*60*24*365)? Ext.ux.RapidApp.userPrefs.dateFormat : Ext.ux.RapidApp.userPrefs.nearDateFormat;
		return '<span class="RapidApp-dt"><s>'+utc+'</s>'+dt.format(fmt)+'</span>';
	} catch (err) {
		return dateStr + " GMT";
	}
}




/*  Ext.ux.RapidApp.AjaxCmp
 Works like Ext.ux.AutoPanel except renders directly to the
 Element object instead of being added as an item to the
 Container
*/
Ext.ux.RapidApp.AjaxCmp = Ext.extend(Ext.Component, {
	
	autoLoad: null,
	
	applyCnf: {},
	
	// deleteId: If set to true the ID of the dynamically fetched
	// component will be deleted before its created
	deleteId: false,
	
	initComponent: function() {
		if(!Ext.isObject(this.autoLoad)) { throw 'autoLoad must be an object' };
		if(!Ext.isObject(this.applyCnf)) { throw 'applyCnf must be an object' };
		
		this.ajaxReq = {
			disableCaching: true,
			success: function(response, opts) {
				if(response.responseText) { 
					var cmpconf = Ext.decode(response.responseText);
					if(!Ext.isObject(cmpconf)) { throw 'responseText is not a JSON encoded object'; }
					
					// preserve plugins:
					if (Ext.isArray(cmpconf.plugins) && Ext.isArray(this.applyCnf.plugins)) {
						Ext.each(cmpconf.plugins,function(plugin) {
							this.applyCnf.plugins.push(plugin);
						},this);
					}
					
					Ext.apply(cmpconf,this.applyCnf);
					cmpconf.renderTo = this.getEl();
					
					if(this.deleteId && cmpconf.id) { delete cmpconf.id };
					
					var Cmp = Ext.ComponentMgr.create(cmpconf,'panel');
					this.component = Cmp;
					Cmp.relayEvents(this,this.events);
					Cmp.show();
				}
			},
			scope: this
		};
		Ext.apply(this.ajaxReq,this.autoLoad);
		
		this.on('afterrender',function() {
			Ext.Ajax.request(this.ajaxReq);
		},this);
		Ext.ux.RapidApp.AjaxCmp.superclass.initComponent.apply(this, arguments);
	}
});
Ext.reg('ajaxcmp',Ext.ux.RapidApp.AjaxCmp);

/* 
 This works just like checkbox except it renders a simple div and toggles a class
 instead of using a real "input" type=checkbox element. I needed to create this because
 I couldn't get normal checkbox to work properly within AppDV - 2011-05-29 by HV
*/
Ext.ux.RapidApp.LogicalCheckbox = Ext.extend(Ext.form.Checkbox,{
	defaultAutoCreate : { tag: 'div', cls: 'x-logical-checkbox ra-icon-checkbox-clear' },
	
	onRender: function(ct, position) {
		if(this.value == "0") { this.value = false; }
		if(typeof this.value !== 'undefined') { this.checked = this.value ? true : false; }
		Ext.ux.RapidApp.LogicalCheckbox.superclass.onRender.apply(this,arguments);
	},
	
	setValue: function(v) {
		Ext.ux.RapidApp.LogicalCheckbox.superclass.setValue.apply(this,arguments);
		if (v) {
			this.el.replaceClass('ra-icon-checkbox-clear','ra-icon-checkbox');
		}
		else {
			this.el.replaceClass('ra-icon-checkbox','ra-icon-checkbox-clear');
		}
	},
	onClick: function() {
		if (this.checked) {
			this.setValue(false);
		}
		else {
			this.setValue(true);
		}
	}
});
Ext.reg('logical-checkbox',Ext.ux.RapidApp.LogicalCheckbox);


/*
 Ext.ux.RapidApp.menu.ToggleSubmenuItem
 2011-06-08 by HV

 Works like Ext.menu.Item, except the submenu (if defined) is not displayed on mouse-over.
 The item has to be clicked to display the submenu, and then it stays displayed until the item
 is clicked a second time or if the user clicks outside the menu. This is in contrast to the
 normal Item submenu behavior which operates on mouse-over and disapears if you accidently
 move the mouse outside the border of the item and the menu (which is really easy to do when
 you move the cursor from the item to the menu, and is very frustrating to users).

 This class also provides a loading icon feature which will convert the item icon into a loading
 spinner icon after the item is clicked until the sub menu is shown. This is useful because it
 can sometimes take several seconds to show the menu when there are are lot of items.

 If there is no 'menu' or if a handler is defined, this class behaves exactly the same as
 Ext.menu.Item
*/
Ext.ns('Ext.ux.RapidApp.menu');
Ext.ux.RapidApp.menu.ToggleSubmenuItem = Ext.extend(Ext.menu.Item,{
	
	submenuShowPending: false,
	showMenuLoadMask: null,
	loadingIconCls: 'ra-icon-loading', // <-- set this to null to disable the loading icon feature
	
	initComponent: function() {
		if(this.menu && !this.handler) {
			
			this.itemCls = 'x-menu-item x-menu-item-arrow';
			
			this.origMenu = this.menu;
			delete this.menu;
			
			if (typeof this.origMenu.getEl != "function") {
				this.origMenu = new Ext.menu.Menu(this.origMenu);
			}
			
			this.origMenu.on('show',this.onSubmenuShow,this);
			this.origMenu.allowOtherMenus = true;
			
			this.handler = function(btn) {
				if(this.submenuShowPending) { return; }
				
				if(this.origMenu.isVisible()) {
					this.origMenu.hide();
					this.setShowPending(false);
				}
				else {
					this.setShowPending(true);
					this.origMenu.show.defer(100,this.origMenu,[btn.getEl(),'tr?']);
				}
			}
      
      this.on('afterrender',this.hookParentMenu,this);
		}
		Ext.ux.RapidApp.menu.ToggleSubmenuItem.superclass.initComponent.call(this);
	},
  
  // Manually hook into the parent menu and hide when it does. We broke
  // ties with the parent menu on purpose to achieve the toggle functionality
  // so we need to manually reconnect with the hide event
  hookParentMenu: function() {
    if(this.parentMenu) {
      this.parentMenu.on('hide',this.origMenu.hide,this.origMenu);
    }
  },
	
	onSubmenuShow: function() {
		this.setShowPending(false);
	},
	
	setShowPending: function(val) {
		if(val) {
			this.submenuShowPending = true;
			if(this.loadingIconCls) {
				this.setIconClass(this.loadingIconCls);
			}
		}
		else {
			this.submenuShowPending = false;
			if(this.loadingIconCls) {
				this.setIconClass(this.initialConfig.iconCls);
			}
		}
	}
});
Ext.reg('menutoggleitem',Ext.ux.RapidApp.menu.ToggleSubmenuItem);


/*

Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.GridSelectSetDialog = Ext.extend(Ext.Window, {

	grid: null,
	initSelectedIds: [],
	
	//private:
	selectedIdMap: {},
	localGrid: null,
	localFields: null,
	localStore: null,
		
	layout: 'hbox',
	layoutConfig: {
		align: 'stretch',
		pack: 'start'
	},
		
	initComponent: function() {
		
		this.selectedIdMap = {};
		Ext.each(this.initSelectedIds,function(id){
			this.selectedIdMap[id] = true;
		},this);
		
		var grid = this.grid;
		var cmConfig = grid.getColumnModel().config;
		
		this.localFields = [];
		
		Ext.each(cmConfig,function(item) {
			this.localFields.push({ name: item.dataIndex });
		},this);
		
		this.localStore = new Ext.data.JsonStore({ fields: this.localFields });
		
		var cmp = this;
		
		this.localGrid = {
			flex: 1,
			xtype: 'grid',
			store: this.localStore,
			columns: cmConfig,
			autoExpandColumn: grid.autoExpandColumn,
			viewConfig: grid.viewConfig,
			listeners: {
				rowdblclick: function(grid,index,e) {
					var Record = grid.getStore().getAt(index);
					cmp.unSelect(Record);
				}
			}
		};
		
		grid.flex = 1;
		
		this.items = [
			this.localGrid,
			grid
		];
		
		grid.getStore().on('load',this.applyFilter,this);
		
		grid.on('rowdblclick',function(grid,index,e) {
			var Record = this.grid.getStore().getAt(index);
			this.addSelected(Record);
		},this);
		
		Ext.ux.RapidApp.GridSelectSetDialog.superclass.initComponent.call(this);
	},
	
	applyFilter: function() {
		var Store = this.grid.getStore();
		Store.filter([{
			fn: function(Record) {
				return ! this.selectedIdMap[Record.get(Store.idProperty)];
			},
			scope: this
		}]);
	},
	
	addSelected: function(Record) {
		var Store = this.grid.getStore();
		this.localStore.add(Record);
		this.selectedIdMap[Record.data[Store.idProperty]] = true;
		this.applyFilter();
	},
	
	unSelect: function(Record) {
		var Store = this.grid.getStore();
		this.localStore.remove(Record);
		delete this.selectedIdMap[Record.data[Store.idProperty]];
		this.applyFilter();
	}
	
});
Ext.reg('grid-selectset-dialog',Ext.ux.RapidApp.GridSelectSetDialog);

*/



Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.AppGridSelector = Ext.extend(Ext.Container, {

	grid: null,
	initSelectedIds: [],
	
	dblclickAdd: true,
	dblclickRemove: false,
	
	leftTitle: 'Selected',
	leftIconCls: 'ra-icon-checkbox-yes',
	rightTitle: 'Not Selected',
	rightIconCls: 'ra-icon-checkbox-no',
	
	showCountsInTitles: true,
	
	baseParams: {},
	
	//private:
	selectedIdMap: {},
	localGrid: null,
	localFields: null,
	localStore: null,
		
	// Emulate border layout:
	style: { 'background-color': '#f0f0f0' },
		
	layout: 'hbox',
	layoutConfig: {
		align: 'stretch',
		pack: 'start'
	},

	initComponent: function() {
		
		this.addEvents( 'selectionsave' );
		
		var cmp = this;
		
		this.selectedIdMap = {};
		Ext.each(this.initSelectedIds,function(id){
			this.selectedIdMap[id] = true;
		},this);
		
		var grid = this.grid;
		var cmConfig = grid.getColumnModel().config;
		var store = grid.getStore();

		this.localFields = [];
		
		Ext.each(cmConfig,function(item) {
			this.localFields.push({ name: item.dataIndex });
		},this);
		
		this.localStore = new Ext.data.JsonStore({ 
			fields: this.localFields,
			api: store.api,
			listeners: {
				beforeload: function(Store,opts) {
					Store.baseParams['id_in'] = Ext.encode(cmp.getSelectedIds());
				}
			}
		});
		
		//Apply any baseParams to the store:
		Ext.iterate(this.baseParams,function(k,v) {
			this.localStore.setBaseParam(k,v);
			store.setBaseParam(k,v);
		},this);
		
		this.on('afterrender',function(){ this.localStore.load(); },this);
		
		this.localGrid = new Ext.grid.GridPanel({
			xtype: 'grid',
			store: this.localStore,
			columns: cmConfig,
			autoExpandColumn: grid.autoExpandColumn,
			enableHdMenu: false,
			enableColumnMove: false,
			viewConfig: grid.viewConfig
		});
		
		this.addButton = new Ext.Button({
			text: 'Add',
			iconCls: 'ra-icon-arrow-left',
			iconAlign: 'left',
			handler: function() {
				cmp.addRowsSelected.call(cmp);
			},
			disabled: true
		});
		
		this.removeButton = new Ext.Button({
			text: 'Remove',
			iconCls: 'ra-icon-arrow-right',
			iconAlign: 'right',
			handler: function() {
				cmp.removeRowsSelected.call(cmp);
			},
			disabled: true
		});
		
		this.items = [
			{
				
				itemId: 'left-panel',
				title: this.leftTitle,
				iconCls: this.leftIconCls,
				flex: 1,
				layout: 'fit',
				hideBorders: true,
				items: this.localGrid,
				margins:{
					top: 0,
					right: 5,
					bottom: 0,
					left: 0
				},
				buttons: [
					this.removeButton,
					' ',' ',' ' // <-- spacing
				]
			},
			{
				itemId: 'right-panel',
				title: this.rightTitle,
				iconCls: this.rightIconCls,
				flex: 1,
				layout: 'fit',
				hideBorders: true,
				items: grid,
				buttonAlign: 'left',
				buttons: [
					' ',' ',	' ', // <-- spacing
					this.addButton,
					'->',
					{
						text: 'Save & Close',
						handler: function() {
							cmp.fireEvent('selectionsave',cmp.getSelectedIds());
							cmp.tryClosePage();
						}
					},
					{
						text: 'Cancel',
						handler: function() {
							cmp.tryClosePage();
						}
					}
				]
			}
		];
		
		store.on('load',this.applyFilter,this);
			
		if(this.dblclickRemove) {
			this.localGrid.on('rowdblclick',function(grid,index,e) {
				var Record = grid.getStore().getAt(index);
				cmp.unSelect(Record);
			},this);
		}
		
		if(this.dblclickAdd) {
			grid.on('rowdblclick',function(grid,index,e) {
				var Record = this.grid.getStore().getAt(index);
				this.addSelected(Record);
			},this);
		}
		
		var localSelMod = this.localGrid.getSelectionModel();
		var selMod = this.grid.getSelectionModel();
		
		localSelMod.on('selectionchange',this.onSelectionChange,this);
		selMod.on('selectionchange',this.onSelectionChange,this);
		
		// When one grid is clicked clear the other:
		localSelMod.on('rowselect',function(){ selMod.clearSelections(); },this);
		selMod.on('rowselect',function(){ localSelMod.clearSelections(); },this);
		
		Ext.ux.RapidApp.AppGridSelector.superclass.initComponent.call(this);
	},
	
	applyFilter: function() {
		var Store = this.grid.getStore();
		Store.filter([{
			fn: function(Record) {
				return ! this.selectedIdMap[Record.get(Store.idProperty)];
			},
			scope: this
		}]);
		this.updateTitleCounts();
	},
	
	addRowsSelected: function() {
		var sm = this.grid.getSelectionModel();
		Ext.each(sm.getSelections(),function(Record) {
			this.addSelected(Record);
		},this);
	},
	
	removeRowsSelected: function() {
		var sm = this.localGrid.getSelectionModel();
		Ext.each(sm.getSelections(),function(Record) {
			this.unSelect(Record);
		},this);
	},
	
	addSelected: function(Record) {
		var Store = this.grid.getStore();
		this.localStore.add(Record);
		this.selectedIdMap[Record.data[Store.idProperty]] = true;
		this.applyFilter();
	},
	
	unSelect: function(Record) {
		var Store = this.grid.getStore();
		this.localStore.remove(Record);
		delete this.selectedIdMap[Record.data[Store.idProperty]];
		this.applyFilter();
	},
	
	getSelectedIds: function() {
		var ids = [];
		Ext.iterate(this.selectedIdMap,function(k,v){
			if(v) { ids.push(k); }
		},this);
		return ids;
	},
	
	onSelectionChange: function(sm) {
		this.leftSelectionCheck.call(this);
		this.rightSelectionCheck.call(this);
	},
	
	leftSelectionCheck: function() {
		var sm = this.localGrid.getSelectionModel();
		this.removeButton.setDisabled(!sm.hasSelection());
	},
	
	rightSelectionCheck: function() {
		var sm = this.grid.getSelectionModel();
		this.addButton.setDisabled(!sm.hasSelection());
	},
	
	tryClosePage: function() {
		if (! this.ownerCt) { return; }
		if (this.ownerCt.closable) { return this.ownerCt.close(); }
		if (! this.ownerCt.ownerCt) { return; }
		if (this.ownerCt.ownerCt.closable) { return this.ownerCt.ownerCt.close(); }
	},
	
	getSelectedCount: function() {
		var count = 0;
		Ext.iterate(this.selectedIdMap,function() { count++; });
		return count;
	},
	
	updateTitleCounts: function() {
		if(! this.showCountsInTitles) { return; }
		
		var total = this.grid.getStore().getTotalCount();
		var selected = this.getSelectedCount();
		var adjusted = total - selected;
		
		this.getComponent('left-panel').setTitle(this.leftTitle + ' (' + selected + ')');
		
		var right_panel = this.getComponent('right-panel');
		if(selected > total) {
			right_panel.setTitle(this.rightTitle);
		}
		else {
			right_panel.setTitle(this.rightTitle + ' (' + adjusted + ')');
		}
	}
	
});
Ext.reg('appgridselector',Ext.ux.RapidApp.AppGridSelector);

Ext.ux.RapidApp.PagingToolbar = Ext.extend(Ext.PagingToolbar,{

	allowChangePageSize: true,
	maxPageSize: 500,
  enableOverflow: true,


	initComponent: function() {
    this.layout = 'ra_toolbar';

		if(this.allowChangePageSize) {

			var paging = this;
			
			var suffix_str = '/<span style="font-size:.9em;vertical-align:top;">' +
				'page' +
			'</span>';

			this.pageSizeField = new Ext.form.NumberField({
				itemCls: 'rapp-margin-bottom-0',
				fieldLabel: 'Items per page',
				width: 35,
				maxValue: this.maxPageSize,
				minValue: 1,
				regex: /^\d+$/, // <-- only allow integers
				enableKeyEvents:true,
				listeners:{
					keyup:{
						buffer: 150,
						fn: function(field, e) {
							if (Ext.EventObject.ENTER == e.getKey()){
								if(field.validate()) {
									var size = field.getValue();
									if (size != paging.pageSize) {
										paging.pageSize = size;
                    paging.pageSizeButton.setText(size + suffix_str);
										paging.doLoad();
                    var ovrMenu = field.ownerCt.parentMenu;
                    // Handle special overflow case: hide the menu
                    if(ovrMenu) {
                      ovrMenu.hide();
                    }
									}
									field.ownerCt.hide();
								}
								else {
									field.markInvalid();
								}
							}
						}
					}
				}
			});

			var orig_text = this.beforePageText;
			if(paging.pageSize) { orig_text = paging.pageSize + suffix_str; }
			
			this.pageSizeButton = new Ext.Button({
				text: orig_text,
				style: 'font-size:.9em;',
				menu: {
					layout: 'form',
					showSeparator: false,
					labelAlign: 'right',
					labelWidth: 90,
					items: this.pageSizeField,
					listeners: {
						beforeshow: function(menu) {
							//Disable the menu keyNav to allow arrow keys to work in fields within the menu:
							if(menu.keyNav){ menu.keyNav.disable(); }
							paging.pageSizeField.setValue(paging.pageSize);
						},
						show: function() {
							paging.pageSizeField.focus('',200);
						}
					}
				}
			});
		}
		
		
		this.beforePageText = '';
		this.displayMsg = '{0} - {1} of <span style="font-size:1.1em;color:#083772;">{2}</span>';
		
		// place the query time label immediately after 'refresh'
		this.prependButtons = false;
		this.items = this.items || [];
		paging.queryTimeLabel = new Ext.Toolbar.TextItem({
			text: '',
      cls: 'ra-grid-tb-query-time'
		});
		this.items.unshift(paging.queryTimeLabel);
		
		Ext.ux.RapidApp.PagingToolbar.superclass.initComponent.call(this);
		
    this.insert(this.items.getCount() - 1,this.pageSizeButton,' ');
		
		this.store.on('load',function(store) {
			if(store.reader && store.reader.jsonData) {
				//'query_time' is returned from the server, see DbicLink2
				var query_time = store.reader.jsonData.query_time;
				if(query_time) {
					paging.queryTimeLabel.setText('query&nbsp;time ' + query_time);
				}
				else {
					paging.queryTimeLabel.setText('');
				}
			}
      this.autoSizeInputItem();
		},this);
		
		this.store.on('exception',function(store) {
			paging.queryTimeLabel.setText('--');
		},this);

    // --- NEW: update paging counts in-place (Github Issue #18)
    this.store.on('add',function(store,records,index) {
      this.store.totalLength = this.store.totalLength + records.length;
      this.updateInfo();
    },this);

    this.store.on('remove',function(store,record,index) {
      this.store.totalLength--;
      this.updateInfo();
    },this);
    // ---

    this.inputItem.on('afterrender',this.autoSizeInputItem,this);
    this.inputItem.on('keydown',this.autoSizeInputItem,this,{buffer:20});
    this.inputItem.on('blur',this.autoSizeInputItem,this);
    this.on('change',this.onPageDataChange,this);
	},

  doRefresh: function() {
    // Added for Github Issue #13
    // Special handling for DataStorePlus cached total counts. Clear
    // it whenever the user manually clicks 'Refresh' in the toolbar
    if(this.store.cached_total_count) {
      delete this.store.cached_total_count;
    }
    return Ext.ux.RapidApp.PagingToolbar.superclass.doRefresh.apply(this,arguments);
  },

  // NEW: override private method 'updateInfo()' to commify values 
  // (Added for Github Issue #15)
  updateInfo : function(){
    if(this.displayItem){
      var count = this.store.getCount();
      var msg = count == 0 ?
        this.emptyMsg :
        String.format(
          this.displayMsg,
          Ext.util.Format.number(this.cursor+1,'0,000'), 
          Ext.util.Format.number(this.cursor+count,'0,000'), 
          Ext.util.Format.number(this.store.getTotalCount(),'0,000')
        );
      this.displayItem.setText(msg);
    }
  },

  // Sets the width of the input (current page) dynamically
  autoSizeInputItem: function() {
    var val = this.inputItem.getValue();
    // 14px wide, plus 6px for each character:
    var size = 14 + (6 * [val].join('').length);
    if (size < 20) { size = 20; }
    // Max width 60px (enough for 8 digits)
    if (size > 60) { size = 60; }
    this.inputItem.setWidth(size);
    this.syncSize();
  },

  onPageDataChange: function(tb,d) {
    // Set the "afterPageText" again, but this time commified:
    this.afterTextItem.setText(String.format(
      this.afterPageText,
      Ext.util.Format.number(d.pages,'0,000')
    ));

    // Update the max value of the input item:
    this.inputItem.setMaxValue(d.pages);
    
    this.syncSize();
  }

});
Ext.reg('rapidapp-paging',Ext.ux.RapidApp.PagingToolbar);


Ext.ux.RapidApp.IconClsRenderFn = function(val) {
	if (val == null || val === "") { return Ext.ux.showNull(val); }
	//return '<div style="width:16px;height:16px;" class="' + val + '"></div>';
	return '<div class="with-icon ' + val + '">' + val + '</div>';
}


/********************************************************************/
/***********  -- vvv -- Ext.ux.grid.PropertyGrid -- vvv -- **********/

/* http://www.extjs.com/forum/showthread.php?t=41390 */
Ext.namespace('Ext.ux.grid');
Ext.ux.grid.PropertyRecord = Ext.data.Record.create([
    {name:'name',type:'string'}, 'value', 'header', 'field'
]);

Ext.ux.grid.PropertyStore = function(grid, source){
    this.grid = grid;
    this.store = new Ext.data.Store({
        recordType : Ext.grid.PropertyRecord
    });

        this.store.loadRecords = function(o, options, success){
        if(!o || success === false){
            if(success !== false){
                this.fireEvent("load", this, [], options);
            }
            if(options.callback){
                options.callback.call(options.scope || this, [], options, false);
            }
            return;
        }

        var r = o.records, t = o.totalRecords || r.length;

        if(!options || options.add !== true){
            if(this.pruneModifiedRecords){
                this.modified = [];
            }

            for(var i = 0, len = r.length; i < len; i++){
                r[i].join(this);
            }

            if(this.snapshot){
                this.data = this.snapshot;
                delete this.snapshot;
            }

            this.data.clear();
            this.data.addAll(r);
            this.totalLength = t;
            //this.applySort();
            this.fireEvent("datachanged", this);

        }else{
            this.totalLength = Math.max(t, this.data.length+r.length);
            this.add(r);
        }

        this.fireEvent("load", this, r, options);

        if(options.callback){
            options.callback.call(options.scope || this, r, options, true);
        }
    };

    this.store.on('update', this.onUpdate,  this);
    if(source){
        this.setSource(source);
    }

    Ext.ux.grid.PropertyStore.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.grid.PropertyStore, Ext.util.Observable, {
    setSource : function(o,fields){
        this.source = o;
        // -- removed by HV -- 
        // this doesn't seem to be needed and causes the page to jump around:
        //this.store.removeAll();
        var data = [];

        if (fields) {
            for (var k in fields) {
                k=fields[k];
                if (typeof(k) == 'object'){
                //if (k.id && this.isEditableValue(o[k.dataIndex])) {
                    data.push(new Ext.grid.PropertyRecord({
                        name: k.dataIndex,
                        value: o[k.dataIndex],
                        header: k.header,
                        field: k
                    }, k.id));
                }
            }
        } else {
            for (var k in o) {
                if (this.isEditableValue(o[k])) {
                    data.push(new Ext.grid.PropertyRecord({
                        name: k,
                        value: o[k],
                        header: k
                    }, k));
                }
            }
        }
        this.store.loadRecords({records: data}, {}, true);
    },

    onUpdate : function(ds, record, type){
        if(type == Ext.data.Record.EDIT){
            var v = record.data['value'];
            var oldValue = record.modified['value'];
            if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
                this.source[record.id] = v;
                record.commit();
                this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
            }else{
                record.reject();
            }
        }
    },

    getProperty : function(row){
       return this.store.getAt(row);
    },

    isEditableValue: function(val){
        if(Ext.isDate(val)){
            return true;
        }else if(typeof val == 'object' || typeof val == 'function'){
            return false;
        }
        return true;
    },

    setValue : function(prop, value){
        this.source[prop] = value;
        this.store.getById(prop).set('value', value);
    },

    getSource : function(){
        return this.source;
    }
});

Ext.ux.grid.PropertyColumnModel = function(grid, store){
    this.grid = grid;
    var g = Ext.grid;
    var f = Ext.form;
    this.store = store;
    
    Ext.ux.grid.PropertyColumnModel.superclass.constructor.call(this, [
        {header: this.nameText, width:grid.nameWidth, fixed:true, sortable: true, dataIndex:'header', id: 'name', menuDisabled:true},
        {header: this.valueText, width:grid.valueWidth, resizable:false, dataIndex: 'value', id: 'value', menuDisabled:true}
    ]);

    this.booleanEditor = new Ext.form.ComboBox({
            triggerAction : 'all',
            mode : 'local',
            valueField : 'boolValue',
            displayField : 'name',
            editable:false,
            selectOnFocus: true,
            forceSelection: true,
            store : {
                xtype : 'arraystore',
                idIndex : 0,
                fields : ['boolValue','name'],
                data : [[false,'false'],[true,'true']]
                }
    });

    this.editors = {
        'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
        'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
        'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
        'boolean' : new g.GridEditor(this.booleanEditor)
    };

    this.renderCellDelegate = this.renderCell.createDelegate(this);
    this.renderPropDelegate = this.renderProp.createDelegate(this);
};

Ext.extend(Ext.ux.grid.PropertyColumnModel, Ext.grid.ColumnModel, {
    nameText : 'Name',
    valueText : 'Value',
    dateFormat : 'j/m/Y',

    renderDate : function(dateVal){
        return dateVal.dateFormat(this.dateFormat);
    },

    renderBool : function(bVal){
        return bVal ? 'true' : 'false';
    },

    isCellEditable : function(colIndex, rowIndex){
            var p = this.store.getProperty(rowIndex);
            if (p.data.field && p.data.field.editable == false) {
                    return false;
                }
        return colIndex == 1;
    },

    getRenderer : function(col){
        return col == 1 ? this.renderCellDelegate : this.renderPropDelegate;
    },

    renderProp : function(v){
        return this.getPropertyName(v);
    },

    renderCell : function(val, metadata, record, rowIndex, colIndex, store){
        if (record.data.field && typeof(record.data.field.renderer) == 'function'){
            return record.data.field.renderer.call(this, val, metadata, record, rowIndex, colIndex, store);
        }

        var rv = val;
        if(Ext.isDate(val)){
            rv = this.renderDate(val);
        }else if(typeof val == 'boolean'){
            rv = this.renderBool(val);
        }
        return Ext.util.Format.htmlEncode(rv);
    },

    getPropertyName : function(name){
        var pn = this.grid.propertyNames;
        return pn && pn[name] ? pn[name] : name;
    },

    getCellEditor : function(colIndex, rowIndex){
        var p = this.store.getProperty(rowIndex);
        var n = p.data['name'], val = p.data['value'];
        if(p.data.field && typeof(p.data.field.editor) == 'object'){
            return p.data.field.editor;
        }

        if(typeof(this.grid.customEditors) == 'function'){
            return this.grid.customEditors(n);
        }

        if(Ext.isDate(val)){
            return this.editors['date'];
        }else if(typeof val == 'number'){
            return this.editors['number'];
        }else if(typeof val == 'boolean'){
            return this.editors['boolean'];
        }else{
            return this.editors['string'];
        }
    },

    destroy : function(){
        Ext.ux.grid.PropertyColumnModel.superclass.destroy.call(this);
        for(var ed in this.editors){
            Ext.destroy(this.editors[ed]);
        }
    }
});

Ext.ux.grid.PropertyGrid = Ext.extend(Ext.grid.EditorGridPanel, {
    enableColumnMove:false,
    stripeRows:false,
    trackMouseOver: false,
    clicksToEdit:1,
    enableHdMenu : false,
    editable: true,
    nameWidth: 120,
    valueWidth: 50,
    source: {},
    autoExpandColumn: 'value',

    initComponent : function(){
        this.customEditors = this.customEditors || {};
        this.lastEditRow = null;
        var store = new Ext.ux.grid.PropertyStore(this);
        this.propStore = store;
        var cm = new Ext.ux.grid.PropertyColumnModel(this, store);
        store.store.sort('name', 'ASC');
        this.addEvents(
            'beforepropertychange',
            'propertychange'
        );
        this.cm = cm;
        this.ds = store.store;
        Ext.ux.grid.PropertyGrid.superclass.initComponent.call(this);

        this.selModel.on('beforecellselect', function(sm, rowIndex, colIndex){
            if(colIndex === 0){
                this.startEditing.defer(200, this, [rowIndex, 1]);
                return false;
            }
        }, this);
                if (!this.editable){
                    this.on('beforeedit', function(){return false})
                }
    },

    onRender : function(){
        Ext.ux.grid.PropertyGrid.superclass.onRender.apply(this, arguments);
        this.getGridEl().addClass('x-props-grid');
    },

    afterRender: function(){
        Ext.ux.grid.PropertyGrid.superclass.afterRender.apply(this, arguments);
        if(this.source){
            this.setSource(this.source);
        }
    },

    setSource : function(source){
        this.propStore.setSource(source,this.fields);
    },

    load : function(source){
        this.setSource(source);
    },

    loadRecord : function(record) {
        record.data && this.setSource(record.data);
    },

    getSource : function(){
        return this.propStore.getSource();
    },

    setEditable: function(rowIndex, editable) {
      var p = this.store.getProperty(rowIndex);
      if(p.data.field) p.data.field.editable = editable;
    }
});
Ext.reg("propertygrid2", Ext.ux.grid.PropertyGrid);

/***********  -- ^^^ -- Ext.ux.grid.PropertyGrid -- ^^^ -- **********/
/********************************************************************/


/* GLOBAL OVERRIDE!!! 
We always want to hide the contents of the grid cell while we're editing it...
*/
Ext.override(Ext.grid.GridEditor,{
	hideEl: true
});


Ext.ns('Ext.ux.RapidApp');
Ext.ux.RapidApp.AppPropertyGrid = Ext.extend(Ext.ux.grid.PropertyGrid,{
	
	editable_fields: {},
		
	storeReloadButton: true,
	
	viewConfig: { emptyText: '<span style="color:darkgrey;">(Empty)</span>' },
	
	markDirty: true,
	
	use_edit_form: true,
	
	initComponent: function() {
		
		this.on('beforepropertychange',function(source,rec,n,o) {
			
			// FIXME!!!!!
			
			if(n == null && o == '0') { return false; }
			if(o == null && n == '0') { return false; }
			if(n == true && o == '1') { return false; }
			if(o == true && n == '1') { return false; }
			
			
			
		},this);
		
    // This is a workaround for a spurious race-condition bug that is not fully
    // understood... The root of the issue is that we are tying into the store 
    // earlier than normal, and it appears that very sporadically the 'store' 
    // property is undefined at this point. That is why we fall back to other
    // locations where the 'store' can be found. This is probably a bug someplace
    // else, like in AutoPanel or DataStore, but this seems to be the only place 
    // where we have the problem (again, because no other places do we try to hook 
    // into the store within 'initComponent' but this should work). TODO/FIXME
    this.bindStore = this.store || this.initialConfig.store;
    if(!this.bindStore && this.ownerCt) {
      this.bindStore = this.ownerCt.store || this.ownerCt.initialConfig.store;
    }
		delete this.store;
		
		if(this.storeReloadButton) {
			var store = this.bindStore;
			this.tools = [{
				id: 'refresh',
				qtip: 'Refresh',
				handler: function() {
					store.reload();
				},
				scope: this
			}];
			if(store.api.update){
				this.tools.unshift({
					id: 'gear',
					qtip: 'Edit',
					handler: function() {
						store.editRecordForm();
					},
					scope: this
				});
			}
		}
		
		if(this.columns && ! this.fields) {
			this.fields = this.columns;
			delete this.columns;
		}
		
		var propgrid = this;
		
		var columns = [];
		if(this.bindStore.baseParams.columns) {
			// append to existing column list if set:
			columns = Ext.decode(this.bindStore.baseParams.columns);
		}
		
		// prune/modify fields according to 'no_column'/'allow_edit'/'allow_view' :
		var new_fields = [];
		Ext.each(this.fields,function(field) {
			field.id = field.dataIndex;
			columns.push(field.dataIndex);
			
			// Give the field editor a refernce back to us/the propgrid:
			if(field.editor) { field.editor.propgrid = propgrid; }
			
			// prune out 'no_column' fields without either 'allow_edit' or 'allow_view':
			if(field.no_column && !field.allow_edit && !field.allow_view) { return; }
			
			// prune out fields with 'allow_view' specificially set to false:
			if(typeof field.allow_view !== "undefined" && !field.allow_view) { return; }
			
			field.allow_view = true;
			
			if(typeof field.allow_edit !== "undefined" && !field.allow_edit) { 
				// prune out fields with 'allow_edit' by itself (without aithout allow_view)
				// specificially set to false:
				if(!field.allow_view) { return; }
				
				// Otherwise, remove the editor (if needed):
				if(field.editor) { delete field.editor; }
			}
			
			new_fields.push(field);
		},this);
		this.fields = new_fields;
		
		
		Ext.each(this.fields,function(field) {
			
			var wrapcss;
			// Extra logic to handle editors as simple xtypes and not already 
			// GridEditor objects. This is handled by EditorGridPanel, but not
			// by the PropertyGrid:
			if (field.editor) {
				if (!field.editor.getXType) { 
					field.editor = Ext.ComponentMgr.create(field.editor,'textfield'); 
				}
				if (!field.editor.startEdit){
					field.editor = new Ext.grid.GridEditor({ 
						//autoSize: true, 
						//hideEl: true, 
						field: field.editor
					});
				}
				
				xtype = field.editor.field.xtype;
				wrapcss = ' with-background-right-image ra-icon-gray-pencil';
				if (xtype == 'combo' || xtype == 'appcombo2') {
					wrapcss = ' with-background-right-image ra-icon-gray-down';
				}

				this.editable_fields[field.name] = 1;
			}
			
			var orig_renderer = field.renderer;
			field.renderer = function(value,metaData,record,rowIndex,colIndex) {
				
				// Turn on word-wrap (set to off in a normal grid)
				metaData.attr = 'style="white-space:normal;"';
				
				// Mark dirty like in normal grid:
				var bindRec = propgrid.bindRecord
				if(bindRec && bindRec.dirty && bindRec.modified[record.id]) {
					metaData.css += ' x-grid3-dirty-cell';
				}
				
				// Translate the renderer to work like in a normal grid:
				if(orig_renderer) {
					if(!bindRec) { 
						value = orig_renderer.apply(field,arguments); 
					}
					else {
						value = orig_renderer.call(field,value,metaData,bindRec,0,0,propgrid.bindStore);
					}
				}
				
				if(wrapcss) { value = '<div class="' + wrapcss + '">' + value + '</div>'; }
				return value;
			}
			
			
			
		},this);
		
		if(! this.fields.length > 0) { this.collapsed = true; }
		
		var params = { columns: Ext.encode(columns) };
		
		if(this.baseParams) {
			Ext.apply(params,this.baseParams);
		}
		
		Ext.apply(this.bindStore.baseParams,params);
		
		Ext.ux.RapidApp.AppPropertyGrid.superclass.initComponent.call(this);
		
		/* -- vv -- Make text of the value column selectable (copy/paste) :*/
		// TODO: expand/refine this
		var val_col = this.getColumnModel().getColumnById('value');
		val_col.css = '-moz-user-select: text;-khtml-user-select: text;';
		/* -- ^^ -- */
		
		this.on('afterrender',this.loadFirstRecord,this);
		this.bindStore.on('load',this.loadFirstRecord,this);
		this.bindStore.on('update',this.loadFirstRecord,this);
		this.on('beforeedit',this.onBeforeEdit,this);
		this.on('propertychange',this.onPropertyChange,this);
		
		
		
		
		
		
		
		
		var cmp = this;
		/* COPIED FROM datastore-plus FIXME*/
		/* 
			Property Grids (from DbicAppPropertyPage) aren't normal RapidApp/DataStore2
			modules and so they don't get the datastore-plus plugin. This needs to be
			fixed/refactored. In the mean time, this code is copied verbatim from the
			datastore-plus plugin so that grid editors, specifically the new 'cycle-field'
			and 'menu-field', behave the same as in normal AppGrid2 grids
		*/
		/**********************/
		/** For Editor Grids **/
		if(Ext.isFunction(cmp.startEditing)){
			
			cmp.startEditing_orig = cmp.startEditing;
			
			cmp.startEditing = function(row,col) {
				var ed = this.colModel.getCellEditor(col, row);
				if(ed) {
					var field = ed.field;
					if(field && !field.DataStorePlusApplied) {
						
						// For combos and other fields with a select listener, automatically
						// finish the edit on select
						field.on('select',cmp.stopEditing.createDelegate(cmp));
						
						// For cycle-field/menu-field:
						field.cycleOnShow = false;
						field.manuOnShow = false;
						
						//Call 'expand' for combos and other fields with an expand method (cycle-field)
						if(Ext.isFunction(field.expand)) {
							ed.on('startedit',function(){
								this.expand();
								// If it is specifically a combo, call expand again to make sure
								// it really expands
								if(Ext.isFunction(this.doQuery)) {
									this.expand.defer(50,this);
								}
							},field);
						}
						
						field.DataStorePlusApplied = true;
					}
				}
				return cmp.startEditing_orig.apply(cmp,arguments);
			}
		}
		/**********************/
		/**********************/
		
		
		
		
		
	},
	
	onBeforeEdit: function(e) {
		var field_name = e.record.data.field.name;
		if (this.editable_fields && ! this.editable_fields[field_name]) {
			e.cancel = true;
		}
	},
	
	onPropertyChange: function(source,recordId,value,oldValue) {
		this.bindRecord.beginEdit();
		this.bindRecord.set(recordId,value);
		this.bindRecord.endEdit();
		this.bindRecord.store.saveIfPersist();
	},
	
	getBindStore: function() {
		return this.bindStore;
	},
	
	loadFirstRecord: function() {
		this.bindRecord = this.getBindStore().getAt(0);
		if(!this.bindRecord) { return; }
		this.loadRecord(this.bindRecord.copy());
	}

});
Ext.reg('apppropertygrid', Ext.ux.RapidApp.AppPropertyGrid);


Ext.ux.RapidApp.newXTemplate = function(arg) {
  var tpl = arg;
  var parms = {};
  if(Ext.isArray(arg)) {
    tpl = arg[0];
    parms = arg[1];
  }
  return new Ext.XTemplate(tpl,parms);
}


Ext.ux.RapidApp.renderRed = function(val) {
	return '<span style="color:red;">' + val + '</span>'; 
}

Ext.ux.RapidApp.boolCheckMark = function(val) {
	if (val == null || val === "" || val <= 0) { 
		return '<img src="/assets/rapidapp/misc/static/s.gif" class="ra-icon-12x12 ra-icon-cross-light-12x12">';
	}
	return '<img src="/assets/rapidapp/misc/static/s.gif" class="ra-icon-12x12 ra-icon-checkmark-12x12">';
}

// Returns a date formatter function based on the supplied format:
Ext.ux.RapidApp.getDateFormatter = function(format) {
	if (!format) { format = "Y-m-d H:i:s"; }
	return function(date) {
		var dt = Date.parseDate(date,"Y-m-d H:i:s");
		if (! dt) { return date; }
		return dt.format(format);
	}
}


Ext.ux.RapidApp.renderPencil = function(val) {
	return '<span>' + val + '</span>' + 
		'<img src="/assets/rapidapp/misc/static/s.gif" class="ra-icon-14x14 ra-icon-gray-pencil">';
}


/* -----
 This inline link handler code sets listeners in pure JavaScript on
 generated <a> tags. This is below the Ext level, but returns 'false'
 and sets cancelBubble to override and prevent any other click handlers
 (such as handlers to start editing in an EditorGrid, etc) from firing
 This allows running isolated code. Currently this is just setup for
 custom navigation/content loading but will handle a lot more scenarios
 in the future
*/
Ext.ux.RapidApp.inlineLink = function(href,text,css,style,title) {
	var link = 
		'<a href="' + href + '"' +
		(css ? ' class="' + css + '"' : '') +
		(style ? ' style="' + style + '"' : '') +
		(title ? " title='" + title + "'" : '') +
		' onclick="return Ext.ux.RapidApp.InlineLinkHandler.apply(this,arguments);"' +
		' ondblclick="return Ext.ux.RapidApp.InlineLinkHandler.apply(this,arguments);"' +
		'>' + text + '</a>';
	return link;
}
Ext.ux.RapidApp.InlineLinkHandler = function(e) {
	if (!e) var e = window.event;
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	if(e.type == 'click' && this.hash) {
	
		// --- New: handle simple hashpath URL
		// The reason this is still being done in this function at all is
		// for the code that stops the event from propagating above
		if(this.host == window.location.host && this.hash.search('#!/') == 0) {
			return window.location.href = this.href;
		}
		// ---
		
		var parts = this.hash.split('#loadcfg:data=');
		if(parts.length == 2) {
			var str = parts[1];
			
			// FireFox has automatically already decoded from URI, but Chrome hasn't,
			// making this required:
			str = decodeURIComponent(str);

			var loadCfg = Ext.decode(str);
			var loadTarget = Ext.getCmp("explorer-id").getComponent("load-target");
			loadTarget.loadContent(loadCfg);
		}
	}
	return false;
}

Ext.ux.RapidApp.callFnLink = function(fn_name,text,args,attrs) {
	
	var arg_str = args;
	if(Ext.isArray(args)) {
		arg_str = "'" + args.join("','") + "'";
	}
	
	var func_str = "return " + fn_name + ".call(this," + arg_str + ");";
	
	attrs = attrs || {};
	attrs = Ext.apply({
		href: '#',
		onclick: func_str,
		ondblclick: func_str
	},attrs);
	
	var link = '<a';
	Ext.iterate(attrs,function(k,v) { link += ' ' + k + '="' + v + '"'; });
	link += '>' + text + '</a>';
	
	return link;
}
/* ----- */

/* http://stackoverflow.com/questions/130404/javascript-data-formatting-pretty-printer */
/* Modified by HV */
Ext.ux.RapidApp.DumpObjectIndented = function (obj, indent) {
	var result = "";
	if (indent == null) indent = "";

	for (var property in obj) {
		var value = obj[property];
		if (typeof value == 'string') { 
			value = "'" + value + "'"; 
		}
		else if (typeof value == 'object'){
			if (value instanceof Array) {
				// Just let JS convert the Array to a string!
				value = "[ " + value + " ]";
			}
			else {
				// Recursive dump
				// (replace "  " by "\t" or something else if you prefer)
				var od = Ext.ux.RapidApp.DumpObjectIndented(value, indent + "  ");
				// If you like { on the same line as the key
				//value = "{\n" + od + "\n" + indent + "}";
				// If you prefer { and } to be aligned
				//value = "\n" + indent + "{\n" + od + "\n" + indent + "}";
				value = "{\n" + od + "\n" + indent + "}";
			}
		}
		//result += indent + "'" + property + "' : " + value + ",\n";
		result += indent + property + ": " + value + ",\n";
	}
	return result.replace(/,\n$/, "");
}



/****************************************************************
 * jsDump
 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
 * Date: 5/15/2008
 * @projectDescription Advanced and extensible data dumping for Javascript.
 * @version 1.0.0
 * @author Ariel Flesler
 */
var jsDump;

(function(){
	function quote( str ){
		return '"' + str.toString().replace(/"/g, '\\"') + '"';
	};
	function literal( o ){
		return o + '';	
	};
	function join( pre, arr, post ){
		var s = jsDump.separator(),
			base = jsDump.indent();
			inner = jsDump.indent(1);
		if( arr.join )
			arr = arr.join( ',' + s + inner );
		if( !arr )
			return pre + post;
		return [ pre, inner + arr, base + post ].join(s);
	};
	function array( arr ){
		var i = arr.length,	ret = Array(i);					
		this.up();
		while( i-- )
			ret[i] = this.parse( arr[i] );				
		this.down();
		return join( '[', ret, ']' );
	};
	
	var reName = /^function (\w+)/;
	
	jsDump = {
		parse:function( obj, type ){//type is used mostly internally, you can fix a (custom)type in advance
			var	parser = this.parsers[ type || this.typeOf(obj) ];
			type = typeof parser;			
			
			return type == 'function' ? parser.call( this, obj ) :
				   type == 'string' ? parser :
				   this.parsers.error;
		},
		typeOf:function( obj ){
			var type = typeof obj,
				f = 'function';//we'll use it 3 times, save it
			return type != 'object' && type != f ? type :
				!obj ? 'null' :
				obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions
				obj.getHours ? 'date' :
				obj.scrollBy ?  'window' :
				obj.nodeName == '#document' ? 'document' :
				obj.nodeName ? 'node' :
				obj.item ? 'nodelist' : // Safari reports nodelists as functions
				obj.callee ? 'arguments' :
				obj.call || obj.constructor != Array && //an array would also fall on this hack
					(obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects
				'length' in obj ? 'array' :
				type;
		},
		separator:function(){
			return this.multiline ?	this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
		},
		indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing
			if( !this.multiline )
				return '';
			var chr = this.indentChar;
			if( this.HTML )
				chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
			return Array( this._depth_ + (extra||0) ).join(chr);
		},
		up:function( a ){
			this._depth_ += a || 1;
		},
		down:function( a ){
			this._depth_ -= a || 1;
		},
		setParser:function( name, parser ){
			this.parsers[name] = parser;
		},
		// The next 3 are exposed so you can use them
		quote:quote, 
		literal:literal,
		join:join,
		//
		_depth_: 1,
		// This is the list of parsers, to modify them, use jsDump.setParser
		parsers:{
			window: '[Window]',
			document: '[Document]',
			error:'[ERROR]', //when no parser is found, shouldn't happen
			unknown: '[Unknown]',
			'null':'null',
			undefined:'undefined',
			'function':function( fn ){
				var ret = 'function',
					name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
				if( name )
					ret += ' ' + name;
				ret += '(';
				
				ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
				return join( ret, this.parse(fn,'functionCode'), '}' );
			},
			array: array,
			nodelist: array,
			arguments: array,
			object:function( map ){
				var ret = [ ];
				this.up();
				for( var key in map )
					ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
				this.down();
				return join( '{', ret, '}' );
			},
			node:function( node ){
				var open = this.HTML ? '&lt;' : '<',
					close = this.HTML ? '&gt;' : '>';
					
				var tag = node.nodeName.toLowerCase(),
					ret = open + tag;
					
				for( var a in this.DOMAttrs ){
					var val = node[this.DOMAttrs[a]];
					if( val )
						ret += ' ' + a + '=' + this.parse( val, 'attribute' );
				}
				return ret + close + open + '/' + tag + close;
			},
			functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function
				var l = fn.length;
				if( !l ) return '';				
				
				var args = Array(l);
				while( l-- )
					args[l] = String.fromCharCode(97+l);//97 is 'a'
				return ' ' + args.join(', ') + ' ';
			},
			key:quote, //object calls it internally, the key part of an item in a map
			functionCode:'[code]', //function calls it internally, it's the content of the function
			attribute:quote, //onode calls it internally, it's an html attribute value
			string:quote,
			date:quote,
			regexp:literal, //regex
			number:literal,
			'boolean':literal
		},
		DOMAttrs:{//attributes to dump from nodes, name=>realName
			id:'id',
			name:'name',
			'class':'className'
		},
		HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
		indentChar:'   ',//indentation unit
		multiline:true //if true, items in a collection, are separated by a \n, else just a space.
	};

})();
/** End jsDump
****************************************************************/


Ext.ux.RapidApp.renderJSONjsDump = function(v) {
	try {
		var obj = Ext.decode(v);
		var dump = jsDump.parse( obj );
		return '<pre>' + dump + '</pre>';
	} catch(err) {
		//console.log('ERROR: ' + err);
		return Ext.ux.showNull(v); 
	}
}

Ext.ux.RapidApp.renderMonoText = function(v) {
  return '<pre class="ra-pre-wrap">' + v + '</pre>';
}

Ext.ux.RapidApp.getWithIconClsRenderer = function(icon_cls) {
	return function(value, metaData) {
		if(icon_cls) { metaData.css = 'grid-cell-with-icon ' + icon_cls; }
		return value;
	};
}

Ext.ux.RapidApp.getRendererStatic = function(str,meta) {
	meta = meta || {};
	return function(value,metaData) { 
		Ext.apply(metaData,meta);
		return str; 
	}
}



// Gets the named value in the data set of the field (calling scope),
// whether its a grid, a form, etc. Specific to RapidApp modules
// use like this:
// var value = Ext.ux.RapidApp.fieldContextDataGetValue.call(fieldObj,key);
Ext.ux.RapidApp.fieldContextDataGetValue = function(name) {
	
	var rec_data = {};
		
	// In AppGrid2:
	if(this.gridEditor && this.gridEditor.record) { 
		rec_data = this.gridEditor.record.data;
	}
	
	// In AppDV
	if(this.Record) { 
		rec_data = this.Record.data;
	}
	
	// In AppPropertyGrid
	if(this.propgrid && this.propgrid.bindRecord) { 
		rec_data = this.propgrid.bindRecord.data;
	}
	
	// In a form
	if(this.ownerCt && this.ownerCt.getForm) { 
		var form = this.ownerCt.getForm();
		var field = form.findField(name);
		if (!field) { return null; }
		if(name) { 
			rec_data[name] = field.getValue(); 
		}
		if(this.ownerCt.Record && this.ownerCt.Record.data[name]) {
			if(!rec_data[name] || rec_data[name] == '');
			rec_data = this.ownerCt.Record.data;
		}
	}
	
	return rec_data[name];
}


Ext.ux.RapidApp.winLoadUrlGET = function(cnf) {
	var url = Ext.urlEncode(cnf.params,cnf.url + '?');
	if(!cnf.params) { url = cnf.url; }
	window.open(url,'');
}



// Takes an image tag (html string) and makes it autosize via max-width:100%
Ext.ux.RapidApp.imgTagAutoSizeRender = function(v,maxheight) {
	//if(v.search('<img ') !== 0) { return v; }
	var div = document.createElement('div');
	div.innerHTML = v;
	var domEl = div.firstChild;
	if(domEl && domEl.tagName == 'IMG') { 
		var El = new Ext.Element(domEl);
		var styles = 'max-width:100%;height:auto;width:auto;';
		if(maxheight) { styles += 'max-height:' + maxheight + ';'; }
		El.applyStyles(styles);
		if(El.dom.getAttribute('width')) { El.dom.removeAttribute('width'); }
		if(El.dom.getAttribute('height')) { El.dom.removeAttribute('height'); }
		return div.innerHTML;
	}
	else {
		return v;
	}
}


Ext.ux.RapidApp.getImgTagRendererDefault = function(src,w,h,alt) {
	var def = '<img ';
	if(src){ def += 'src="' + src + '" '; }
	if(w && w != 'autosize'){ def += 'width="' + w + '" '; }
	if(h){ def += 'height="' + h + '" '; }
	if(alt){ def += 'alt="' + alt + '" '; }
	def += '>';
	
	return function(v) {
		if(!v) { return def; }
		if(w == 'autosize') {
			var maxheight = h;
			return Ext.ux.RapidApp.imgTagAutoSizeRender(v); 
		}
		return v;
	}
}






Ext.ux.RapidApp.getRendererPastDatetimeRed = function(format) {
	var renderer = Ext.ux.RapidApp.getDateFormatter(format);
	return function(date) {
		var dt = Date.parseDate(date,"Y-m-d H:i:s");
		if (! dt) { dt = Date.parseDate(date,"Y-m-d"); }
		
		if (! dt) { return Ext.ux.showNull(date); }
		
		var out = renderer(date);
		var nowDt = new Date();
		// in the past:
		if(nowDt > dt) { return '<span style="color:red;">' + out + '</span>'; }
		return out;
	}
}

Ext.ux.RapidApp.num2pct = function(num) {
	if (num != 0 && isFinite(num)) {
		num = Ext.util.Format.round(100*num,2) + '%';
	}
	if(num == 0) { num = '0%'; }
	return num;
}


Ext.ux.RapidApp.NO_DBIC_REL_LINKS = false;

Ext.ux.RapidApp.DbicRelRestRender = function(c) {
	var disp = c.disp || c.record.data[c.render_col];
	var key_value = c.record.data[c.key_col];
	
	if(!c.value) { 
		if(!disp && !key_value) {
			// If everything is unset, including the key_col value itself,
			// we render like a normal empty value. It is only when the 
			// key_col is set but the value/disp is not (indicating a broken
			// or missing link/relationship) that we want to render the special 
			// "unavailable" string (see the following code block) -- SEE UPDATED
			// NOTE BELOW
			return Ext.ux.showNull(key_value);
		}
		c.value = key_value; 
	}
	
	if(!c.value)		{ return disp; }
	if(!disp) 			{ return c.value; }
	if(!c.open_url)	{ return disp; }
	
	var url = '#!' + c.open_url + '/';
	if(c.rest_key) { url += c.rest_key + '/'; }
	
	
	if(c.rs) {
		// multi-rel: no link for 0 records:
		if(c.value == '0') { return disp; }
		// For multi-rel. value actually only contains the count of related
		// rows. key_value will contain the id of the row from which the rs originated
		url += key_value + '/rs/' + c.rs; 
	}
	else {
		// For single-rel
		url += c.value;
	}
	
	if(Ext.ux.RapidApp.NO_DBIC_REL_LINKS) {
		return disp;
	}
	
	return disp + "&nbsp;" + Ext.ux.RapidApp.inlineLink(
		url,
		"<span>open</span>",
		"ra-icon-magnify-tiny",
		null,
		"Open/view: " + disp
	);
}


Ext.ux.RapidApp.DbicSingleRelationshipColumnRender = function(c) {
	var disp = c.record.data[c.render_col];
	var key_value = c.record.data[c.key_col];

	if(!c.value) { 
		if(!disp && !key_value) {
			// If everything is unset, including the key_col value itself,
			// we render like a normal empty value. It is only when the 
			// key_col is set but the value/disp is not (indicating a broken
			// or missing link/relationship) that we want to render the special 
			// "unavailable" string (see the following code block) -- SEE UPDATED
			// NOTE BELOW
			return Ext.ux.showNull(key_value);
		}
		c.value = key_value; 
	}
	
	if(c.value == null && disp == null) {
		// UPDATE: this code path will actually never occur now (after adding the
		// above call to 'showNull'). It will either display the normal null/empty
		// output or the value of the key, so this never happens!! But, after some
		// other improvements to relationship column handling, they now work correctly
		// (I think) with unset values/broken links, which they didn't before, and
		// this alternate display was actually added as a workaround for that problem
		// and is now not even needed/helpful. TODO: after verifying this is in fact true,
		// clean up the logic in this function and remove this and other not-needed
		// code and logic... Also see about applying a special style when the link
		// *is* broken and the key value is being displayed instead of the related
		// render value (I tried to do this already but it wasn't working immediately
		// and I had other, more important things to do at the time)...
		return '<span style="font-size:.90em;color:darkgrey;">' +
			'&times&nbsp;unavailable&nbsp;&times;' +
		'</span>';
	}
	
	if(!c.value)		{ return disp; }
	if(!disp) 			{ return c.value; }
	if(!c.open_url)	{ return disp; }
	
	var loadCfg = { 
		title: disp, 
		autoLoad: { 
			url: c.open_url, 
			params: { ___record_pk: "'" + c.value + "'" } 
		}
	};
		
	var url = "#loadcfg:" + Ext.urlEncode({data: Ext.encode(loadCfg)});

	return disp + "&nbsp;" + Ext.ux.RapidApp.inlineLink(
		url,
		"<span>open</span>",
		"ra-icon-magnify-tiny",
		null,
		"Open/view: " + disp
	);
}

Ext.ux.RapidApp.prettyCsvRenderer = function(v) {
	if(!v) { return Ext.ux.showNull(v); }
	var sep = '<span style="color: navy;font-size:1.2em;font-weight:bold;">,</span> ';
	var list = v.split(',');
	Ext.each(list,function(item){
		// strip whitespace:
		item = item.replace(/^\s+|\s+$/g,"");
	},this);
	return list.join(sep);
}

/************** CUSTOM VTYPES **************/
Ext.apply(Ext.form.VTypes,{
	zipcode: function(v) { return /^\d{5}(-\d{4})?$/.test(v); },
	zipcodeMask: /[0-9\-]+/,
	zipcodeText: 'Zipcode must be 5-digits (e.g. 12345) or 5-digits + 4 (e.g. 12345-6789)'
});
/*******************************************/


Ext.ux.RapidApp.showIframeWindow = function(cnf){
	cnf = Ext.apply({
		src: 'about:blank',
		title: 'Message',
		width: 400,
		height: 225,
		show_loading: false
	},cnf || {});
		
	var win, iframe = document.createElement("iframe");
	iframe.height = '100%';
	iframe.width = '100%';
	iframe.setAttribute("frameborder", '0');
	iframe.setAttribute("allowtransparency", 'true');
	iframe.src = cnf.src;
		
	var winCfg = {
		title: cnf.title,
		modal: true,
		closable: true,
		width: cnf.width,
		height: cnf.height,
		buttonAlign: 'center',
		buttons:[{
			text: 'Ok',
			handler: function(){ win.hide(); win.close(); }
		}],
		contentEl: iframe
	};
	
	if(cnf.show_loading) { 
		winCfg.bodyCssClass = 'loading-background'; 
	}
	
	win = new Ext.Window(winCfg);
	win.show();

};

// Renders a positive, negative, or zero number as green/red/black dash
Ext.ux.RapidApp.increaseDecreaseRenderer = function(v) {
	if (v == null || v === "") { return Ext.ux.showNull(v); }
	if(v == 0) { return	'<span style="color:#333333;font-size:1.3em;font-weight:bolder;">&ndash;</span>'; }
	if(v < 0) { return 	'<span style="color:red;font-weight:bold;">' + v + '</span>'; }
	return 					'<span style="color:green;font-weight:bold;">+' + v + '</span>'; 
};

// Renders pct up tp 2 decimal points (i.e. .412343 = 41.23%) in green or red for +/-
Ext.ux.RapidApp.increaseDecreasePctRenderer = function(val) {
	if (val == null || val === "") { return Ext.ux.showNull(val); }
	var v = Math.round(val*10000)/100;
	if(v == 0) { return	'<span style="color:#333333;font-size:1.3em;font-weight:bolder;">&ndash;</span>'; }
	if(v < 0) { return 	'<span style="color:red;font-weight:bold;">-' + Math.abs(v) + '%</span>'; }
	return 					'<span style="color:green;font-weight:bold;">+' + v + '%</span>'; 
};

// Renders money up tp 2 decimal points (i.e. 41.2343 = $41.23) in green or red for +/-
Ext.ux.RapidApp.increaseDecreaseMoneyRenderer = function(val) {
	if (val == null || val === "") { return Ext.ux.showNull(val); }
	var v = Math.round(val*100)/100;
	if(v == 0) { return	'<span style="color:#333333;font-size:1.3em;font-weight:bolder;">&ndash;</span>'; }
	if(v < 0) { return 	'<span style="color:red;font-weight:bold;">' + Ext.util.Format.usMoney(v) + '</span>'; }
	return 					'<span style="color:green;font-weight:bold;">+' + Ext.util.Format.usMoney(v) + '</span>'; 
};


// Returns the infitity character instead of the value when it is
// a number greater than or equal to 'maxvalue'. Otherwise, the value
// is returned as-is.
Ext.ux.RapidApp.getInfinityNumRenderer = function(maxvalue,type) {
	if(!Ext.isNumber(maxvalue)) { 
		return function(v) { return Ext.ux.showNull(v); }; 
	}
	return function(v) {
		if(Number(v) >= Number(maxvalue)) {
			// also increase size because the default size of the charater is really small
			return '<span title="' + v + '" style="font-size:1.5em;">&infin;</span>';
		}
		
		if(type == 'duration') {
			return Ext.ux.RapidApp.renderDuration(v);
		}
		
		return Ext.ux.showNull(v);
	}
};




Ext.ux.RapidApp.renderDuration = function(seconds,suffixed) {
	if(typeof seconds != 'undefined' && seconds != null && moment) {
		return '<span title="' + seconds + ' seconds">' +
			moment.duration(Number(seconds),"seconds").humanize(suffixed) +
		'</span>'
	}
	else {
		return Ext.ux.showNull(seconds);
	}
}

Ext.ux.RapidApp.renderDurationSuf = function(seconds) {
	return Ext.ux.RapidApp.renderDuration(seconds,true);
}

Ext.ux.RapidApp.renderDurationPastSuf = function(v) {
  if(typeof v != 'undefined' && v != null && moment) {
    var seconds = Math.abs(Number(v));
    return Ext.ux.RapidApp.renderDurationSuf(-seconds);
  }
  else {
    return Ext.ux.showNull(v);
  }
}


// Renders a short, human-readable duration/elapsed string from seconds.
// The seconds are divided up into 5 units - years, days, hours, minutes and seconds -
// but only the first two are shown for readability, since if its '2y, 35d',
// you probably don't care about the exact hours minutes and seconds ( i.e.
// '2y, 35d, 2h, 4m, 8s' isn't all that useful and a lot longer)
Ext.ux.RapidApp.renderSecondsElapsed = function(s) {

  if(!s) {
    return Ext.ux.showNull(s);
  }

  var years = Math.floor    ( s / (365*24*60*60));
  var days  = Math.floor   (( s % (365*24*60*60)) / (24*60*60));
  var hours = Math.floor  ((( s % (365*24*60*60)) % (24*60*60)) / (60*60));
  var mins  = Math.floor (((( s % (365*24*60*60)) % (24*60*60)) % (60*60)  / 60));
  var secs  =             ((( s % (365*24*60*60)) % (24*60*60)) % (60*60)) % 60;
  
  var list = [];
  if(years) { list.push(years + 'y'); }
  if(days)  { list.push(days  + 'd'); }
  if(hours) { list.push(hours + 'h'); }
  if(mins)  { list.push(mins  + 'm'); }
  if(secs)  { list.push(secs  + 's'); }
  
  if (list.length == 0) {
    return Ext.ux.showNull(s);
  }
  else if (list.length == 1) {
    return list[0];
  }
  else {
    return list[0] + ', ' + list[1];
  }
}

// renders a json array of arrays into an HTML Table
Ext.ux.RapidApp.jsonArrArrToHtmlTable = function(v) {

	var table_markup;
	try {
		var arr = Ext.decode(v);
		var rows = [];
		Ext.each(arr,function(tr,r) {
			var cells = [];
			Ext.each(tr,function(td,c) {
				var style = '';
				if(r == 0) {
					style = 'font-size:1.1em;font-weight:bold;color:navy;min-width:50px;';
				}
				else if (c == 0) {
					style = 'font-weight:bold;color:#333333;padding-right:30px;';
				}
				else {
					style = 'font-family:monospace;padding-right:10px;';
				}
				cells.push({
					tag: 'td',
					html: td ? '<div style="' + style + '">' +
						td + '</div>' : Ext.ux.showNull(td)
				})
			});
			rows.push({
				tag: 'tr',
				children: cells
			});
		});
		
		table_markup = Ext.DomHelper.markup({
			tag: 'table',
			cls: 'r-simple-table',
			children: rows
		});
	}catch(err){};

	return table_markup ? table_markup : v;
}

Ext.ux.RapidApp.withFilenameIcon = function(val) {
  var parts = val.split('.');
  var ext = parts.pop().toLowerCase();

  var icon_cls = 'ra-icon-document';

  if(ext == 'pdf')  { icon_cls = 'ra-icon-page-white-acrobat'; }
  if(ext == 'zip')  { icon_cls = 'ra-icon-page-white-compressed'; }
  if(ext == 'xls')  { icon_cls = 'ra-icon-page-white-excel'; }
  if(ext == 'xlsx') { icon_cls = 'ra-icon-page-excel'; }
  if(ext == 'ppt')  { icon_cls = 'ra-icon-page-white-powerpoint'; }
  if(ext == 'txt')  { icon_cls = 'ra-icon-page-white-text'; }
  if(ext == 'doc')  { icon_cls = 'ra-icon-page-white-word'; }
  if(ext == 'docx') { icon_cls = 'ra-icon-page-word'; }
  if(ext == 'iso')  { icon_cls = 'ra-icon-page-white-cd'; }

  return [
    '<span class="with-icon ', icon_cls,'">',
      val,
    '</span>'
  ].join('');
}
Ext.ns('Ext.ux.RapidApp.NavCore');

// This is currently used only by RapidApp::NavCore plugin!

Ext.ux.RapidApp.NavCore.SaveSearchHandler = function(cmp,cnf) {
	
	var save_url = cnf.save_url;
	var search_id = cnf.search_id;
	var pub_allowed = cnf.pub_allowed;
	var is_pub = cnf.is_pub;

	var grid = cmp.findParentByType('appgrid2') || cmp.findParentByType('appgrid2ed');
	if (!grid) { throw("Failed to find parent with XType appgrid2"); }
	
	var target_url = cnf.target_url;
	var target_params = cnf.target_params;
	var target_iconcls = grid.ownerCt.iconCls;
	
	var search_field = {
		xtype			: 'textfield',
		name			: 'search_name',
		itemId		: 'search_name_field',
		labelStyle	: 'text-align:right;',
		fieldLabel	: 'New Search Name'
	};
	
	var pub_checkbox = {
		xtype			: 'checkbox',
		name			: 'public_search',
		itemId		: 'public_search_field',
		fieldLabel	: 'Public Search',
		labelStyle	: 'text-align:right;'
	};
	
	var hide_items = [ search_field ];
	if (pub_allowed) { hide_items.push(pub_checkbox); }
	
	var hide_fieldset = {
		xtype			: 'fieldset',
		itemId		: 'hide_set',
		style			: 'border: none',
		hideBorders	: true,
		labelWidth	: 110,
		border		: false,
		hidden		: true,
		items			: hide_items
	};
	
	
	var checkbox = {
		xtype			: 'checkbox',
		name			: 'create_search',
		fieldLabel	: 'Save-As New Search',
		labelStyle	: 'text-align:right;',
		listeners: {
			check : function(cb,checked) {
				var fset = cb.ownerCt.getComponent("hide_set");
				if (checked) {
					fset.show();
				} else {
					fset.hide();
				}
			}
		}
	};
	
	var items = [ checkbox, hide_fieldset ];
	if (!search_id) {
		hide_fieldset.hidden = false;
		items = [ {xtype:'spacer', height:15 }, hide_fieldset ];
	}
	if(is_pub && ! pub_allowed) {
		checkbox.disabled = true;
		checkbox.checked = true;
		hide_fieldset.hidden = false;
		items = [ checkbox, hide_fieldset ];
	}
	
	var fieldset = {
		xtype			: 'fieldset',
		style			: 'border: none',
		hideBorders	: true,
		labelWidth	: 120,
		border		: false,
		items			: items
	};
	
	var state_data = Ext.encode(grid.getCurSearchData());
	
	return Ext.ux.RapidApp.WinFormPost({
		title: "Save Search",
		height: 195,
		width: 325,
		url: save_url,
		params: {
			cur_search_id: search_id,
			target_url: target_url,
			target_params: target_params,
			target_iconcls: target_iconcls,
			state_data: state_data
		},
		//eval_response: true,
		fieldset: fieldset,
		success: function(response) {
			var loadTarget = grid.findParentByType("apptabpanel");
			// Reload/refresh the tree:
      Ext.ux.RapidApp.NavCore.reloadMainNavTrees();
			
			if (response && response.responseText) {
				var res = Ext.decode(response.responseText);
				// If there is a loadCnf in the JSON packet it means a new search was
				// created and now we need to load it in a new tab:
				if (res && res.loadCnf) {
					loadTarget.loadContent(res.loadCnf);
				}
			}
		}
	});
};


// New handler function for deleting a search - works with AppGrid2
Ext.ux.RapidApp.NavCore.DeleteSearchHandler = function(cmp,url,search_id) {

	var grid = cmp.findParentByType('appgrid2') || cmp.findParentByType('appgrid2ed');
	if (!grid) { throw("Failed to find parent with XType appgrid2"); }
	
	var fn = function() {
		Ext.Ajax.request({
			url: url,
			params: {
				search_id: search_id
			},
			success: function() {
				var loadTarget = grid.findParentByType("apptabpanel");
				loadTarget.closeActive();
				
				// Reload/refresh the tree:
				Ext.ux.RapidApp.NavCore.reloadMainNavTrees();
			
			}
		
		});
	}
	
	return Ext.ux.RapidApp.confirmDialogCall("Delete Search", "Really Delete This Search?", fn);
};


/*
// TODO: put this in rapidapp and handle properly:
Ext.ux.RapidApp.NavCore.reloadMainNavTree = function() {
	//var loadTarget = Ext.getCmp("main-load-target");
	//var tree = loadTarget.getNavsource();
	//if(!tree) { tree = Ext.getCmp('main-nav-tree'); }
	
	Ext.ux.RapidApp.NavCore.reloadMainNavTreeOnly();
	
	// Now reload the manage NavTree, if its loaded, too:
	var tree = Ext.getCmp('manage-nav-tree');
	if(tree) {
		var rootnode = tree.getRootNode();
		tree.getLoader().load(rootnode);
	}
}
*/


Ext.ux.RapidApp.NavCore.reloadMainNavTrees = function() {
	var container = Ext.getCmp('main-navtrees-container');
  container.items.each(function(tree) {
    if(Ext.isFunction(tree.getRootNode)) {
      var rootnode = tree.getRootNode();
      tree.getLoader().load(rootnode);
    }
  });
}



Ext.ns('Ext.ux.RapidApp.AppDV');


Ext.ux.RapidApp.AppDV.DataView = Ext.extend(Ext.DataView, {
	
	// TODO: make cascade recursive like in Ext.Container
	cascade: function(fn,scope,args) {
		fn.apply(scope || this, args || [this]);
		return this;
	},
	
	initComponent: function(){
		Ext.each(this.items,function(item) {
			item.ownerCt = this;
		},this);
		Ext.ux.RapidApp.AppDV.DataView.superclass.initComponent.call(this);
		this.components = [];
		
		this.on('click',this.click_controller,this);
		
		//if(!this.store) { this.store = this.ownerCt.store; }
		
		this.store.on('beforesave',this.onBeforesave,this);
		this.store.on('beforeremove',this.onBeforeremove,this);
		
		// Special AppDV override: addNotAllowed based on
		// current edit record:
		var cmp = this;
		cmp.on('afterrender',function(){
			cmp.store.addNotAllowed = function(){
				if(cmp.currentEditRecord && cmp.currentEditRecord.editing) {
					return true;
				}
				return false;
			}
			/* TODO:
			if(!cmp.store.hasPendingChangesOrig) {
				cmp.store.hasPendingChangesOrig = cmp.store.hasPendingChanges;
			}
			cmp.store.hasPendingChanges = function() {
				//console.log('has pending changes');
				if(cmp.store.addNotAllowed()) { return true; }
				return cmp.store.hasPendingChangesOrig.apply(this,arguments);
			};
			*/
		},this);
		
		this.on('beforeselect',this.onBeforeselect,this);
	},
	
	onBeforeselect: function() {
		// We don't want to allow clicks to toggle record select status when
		// we are editing:
		if(this.currentEditRecord && this.currentEditRecord.editing) {
			return false;
		}
	},
	
	onBeforesave: function() {
		this.isSaving = true;
		this.simulateSaveClick.call(this);
		this.isSaving = false;
	},
	
	refresh: function(){
		Ext.destroy(this.components);
		this.components = [];
		Ext.ux.RapidApp.AppDV.DataView.superclass.refresh.call(this);
		this.renderItems(0, this.store.getCount() - 1);
	},
	
	onUpdate: function(ds, record){
		var index = ds.indexOf(record);
		if(index > -1){
				this.destroyItems(index);
		}
		Ext.ux.RapidApp.AppDV.DataView.superclass.onUpdate.apply(this, arguments);
		if(index > -1){
				this.renderItems(index, index);
		}
		this.toggleDirtyCssRecord(record,true);
	},
	
	onAdd: function(ds, records, index){
		var count = this.all.getCount();
		Ext.ux.RapidApp.AppDV.DataView.superclass.onAdd.apply(this, arguments);
		if(count !== 0){
			this.renderItems(index, index + records.length - 1);
		}
		
		var Record;
		//Get first phantom record:
		Ext.each(records,function(rec) {
			if(Record || !rec.phantom) { return; }
			Record = rec;
		},this);
		
		if(Record) {
			this.currentEditRecord = Record;
			var domEl = this.getNode(Record);
			var editEl = new Ext.Element(domEl);
			this.currentEditEl = editEl;
			this.clearSelections();
			this.handle_edit_record(editEl,editEl,Record,index,editEl);
		}
		
		this.scrollRecordIntoView.defer(10,this,[records[records.length - 1]]);
		this.highlightRecord.defer(10,this,[records]);
		this.toggleDirtyCssRecord(records,true);

	},
	
	forEachRecordNode: function(fn,record) {
		
		if(Ext.isArray(record)){
			Ext.each(record, function(r){
				this.forEachRecordNode(fn,r);
			},this);
			return;
		}
	  
		if(!record || !record.store) { return; }

		var node = this.getNode(record);
		if(!node) { return; }
		var el = new Ext.Element(node);
		fn(el,record);
	},
	
	highlightRecord: function(record) {
		this.forEachRecordNode(function(el){
			el.highlight();
		},record);
	},
	
	puffRecord: function(record) {
		this.forEachRecordNode(function(el){
			el.fadeOut({
				easing: 'easeNone',
				duration: .5,
				remove: false,
				useDisplay: false,
				concurrent: true
			});
			el.highlight();
		},record);
	},
	
	toggleDirtyCssRecord: function(record,tog) {
		var dv = this;
		this.forEachRecordNode(function(el,rec){
			if(rec.dirtyEl) { rec.dirtyEl.remove(); }
			if(tog && el && rec.dirty) {
				
				var domCfg = {
					tag: 'div',
					style: 'position:absolute;',
					children:[{
						tag: 'div',
						cls: 'x-grid3-dirty-cell',
						style: 'position:relative;top:0;left:0;z-index:15000;height:10px;width:10px;'
					}]
				};
				
				if(el.dom.tagName.toUpperCase() == 'TR') {
					domCfg = {
						tag: 'tr',
						children:[{
							tag: 'td',
							children:[domCfg]
						}]
					};
				}
				
				rec.dirtyEl = el.insertSibling(domCfg,'before');
			}
		},record);
	},
	
	onBeforeremove: function(ds, record){
		
		if(this.removeInProgress) { return true; }
		this.toggleDirtyCssRecord(record,false);
		if(record == this.currentEditRecord) {
			var index = this.getStore().indexOf(record);
			this.simulateCancelClick(record,index,this.currentEditEl);
			return false;
		}
		
		this.puffRecord(record);
		
		this.removeInProgress = true;
		var doRemove = function(){
			ds.remove.apply(this,arguments);
			this.removeInProgress = false;
		};
		doRemove.defer(300,this,[record]);
		
		return false;
	},
	onRemove: function(ds, record, index){
		
		this.destroyItems(index);
		Ext.ux.RapidApp.AppDV.DataView.superclass.onRemove.apply(this, arguments);
	},
	
	onDestroy: function(){
		Ext.ux.RapidApp.AppDV.DataView.superclass.onDestroy.call(this);
		Ext.destroy(this.components);
		this.components = [];
	},
	
	renderItems: function(startIndex, endIndex){
		var ns = this.all.elements;
		var args = [startIndex, 0];
		
		//console.dir(args);
		
		for(var i = startIndex; i <= endIndex; i++){
			var r = args[args.length] = [];
			for(var items = this.items, j = 0, len = items.length, c; j < len; j++){
			
				// c = items[j].render ?
				//	c = items[j].cloneConfig() :
					
				// RapidApp specific:
				// Components are stored as serialized JSON to ensure they
				// come out exactly the same every time:
				var itemCnf = Ext.decode(items[j]);
				itemCnf.ownerCt = this;

				// renderDynTarget will look for a child div with class="encoded-params" containing
				// JSON encoded additional params that will be dynamically applied to the
				// config of the component being created. Essentially this allows part or all
				// of the component config to be stored directly within the HTML markup. Typically
				// the encoded-params div will have style="display:none;" to prevent the JSON
				// from showing up on the page.
				if(itemCnf.renderDynTarget) {
					var Node = Ext.DomQuery.selectNode(itemCnf.renderDynTarget, ns[i]);
					if(Node) {

						var cnf = {};
						Ext.apply(cnf,itemCnf);

						var encNode = Ext.DomQuery.selectNode('div.encoded-params', Node);
						if(encNode) {
							Ext.apply(cnf,Ext.decode(encNode.innerHTML));
						}
						
						c = Ext.create(cnf, this.defaultType);
						r[j] = c;
						c.render(Node);
					}
				}
				else {
					c = Ext.create(itemCnf, this.defaultType);
					r[j] = c;
					
					if(c.renderTarget){
						c.render(Ext.DomQuery.selectNode(c.renderTarget, ns[i]));
					}
					else if(c.applyTarget){
						c.applyToMarkup(Ext.DomQuery.selectNode(c.applyTarget, ns[i]));
					}
					else{
						c.render(ns[i]);
					}
				}	
				
				if(c && Ext.isFunction(c.setValue) && c.applyValue){
					c.setValue(this.store.getAt(i).get(c.applyValue));
					c.on(
						'blur', 
						function(f){
							this.store.getAt(this.index).data[this.dataIndex] = f.getValue();
						},
						{store: this.store, index: i, dataIndex: c.applyValue}
					);
				}

			}
		}
		this.components.splice.apply(this.components, args);
	},
	
	destroyItems: function(index){
		Ext.destroy(this.components[index]);
		this.components.splice(index, 1);
	},
	
	get_new_record: function(initData) {
		
		var Store = this.getStore();
		
		// abort if the Store doesn't have create in its API:
		if(!Store.api.create) { return false; } 
		
		var node = this.getNode(0);
		if(node) {
			var nodeEl = new Ext.Element(node);
			// abort if another record is already being updated:
			if(nodeEl.parent().hasClass('record-update')) { return; }
		}
		
		var recMaker = Ext.data.Record.create(Store.fields.items);
		var newRec;
		if(initData){
			newRec = new recMaker(initData);
		}
		else {
			newRec = new recMaker;
		}
		
		if(! Store.api.create) {
			Ext.Msg.alert('Cannot add','No create function has been defined');
			return false;
		}
		return newRec;
	},
	
	add_record: function(initData) {
		var newRec = this.get_new_record(initData);
		if (newRec) {
			return this.getStore().add(newRec);
		}
	},
	
	insert_record: function(initData) {
		var newRec = this.get_new_record(initData);
		if (newRec) {
			return this.getStore().insert(0,newRec);
		}
	},
	
	
	
	set_field_editable: function(editEl,fieldname,index,Record,domEl) {
		
		//abort if its already editing:
		if(editEl.hasClass('editing')) { return; }
		
		var dataWrap = editEl.child('div.data-wrapper');
		var dataEl = editEl.child('div.data-holder');
		var fieldEl = editEl.child('div.field-holder');
		
		
		editEl.addClass('editing');

		var cnf = {};
		Ext.apply(cnf,Ext.decode(this.FieldCmp_cnf[fieldname]));
		Ext.apply(cnf,{
			ownerCt: this,
			Record: Record,
			value: Record.data[fieldname],
			//renderTo: dataWrap
			renderTo: fieldEl
			//contentEl: dataEl
		});
		
		if(!cnf.width) {	cnf.width = dataEl.getWidth(); }
		if(!cnf.height) { cnf.height = dataEl.getHeight(); }
		if(cnf.minWidth) { if(!cnf.width || cnf.width < cnf.minWidth) { cnf.width = cnf.minWidth; } }
		if(cnf.minHeight) { if(!cnf.height || cnf.height < cnf.minHeight) { cnf.height = cnf.minHeight; } }
		
		// UPDATE: using visibility mode across the board now because the other method was
		// causing images to overlap in some cases (2011-10-10 by HV)
		//if(Ext.isIE) {
			dataEl.setVisibilityMode(Ext.Element.DISPLAY);
			dataEl.setVisible(false);
		//}
		//else {
			// Stupid IE can't do it with contentEl, but we want to do the contentEl
			// way because if we use the hide method the element jumps in an
			// ungly way in FF.
		//	cnf.contentEl = dataEl;
		//}
		
		var Store = this.getStore();
		
		var Field = Ext.create(cnf,'field');
		
		if(!Ext.isObject(this.FieldCmp)) { this.FieldCmp = {} }
		if(!Ext.isObject(this.FieldCmp[index])) { this.FieldCmp[index] = {} }
		this.FieldCmp[index][fieldname] = Field;
		
		
		/*****************************************************/
		// don't do this if the entire record is in edit mode or another record is already being updated:
		if(domEl &&(!domEl.hasClass('editing-record') && !domEl.parent().hasClass('record-update'))) { 

			var s = this.currentEditingFieldScope;
			if(s) {
				// cancel editing of any other field already being edited
				this.cancel_field_editable(s.editEl,s.fieldname,s.index,s.Record);
			}
			
			s = {
				editEl: editEl,
				fieldname: fieldname,
				index: index,
				Record: Record
			};
			
			var endEdit = function() {
				this.cancel_field_editable(editEl,fieldname,index,Record);
			};
			
			var saveEndEdit = function() {
				//console.log('saveEndEdit');
				this.save_field_data(editEl,fieldname,index,Record);
				Store.saveIfPersist();
				endEdit.call(this);
			};
			
			this.currentEditingFieldScope = s;
			
			// Setup keymaps for Enter and Esc:
			Field.on('specialkey',function(field,e) {
				if(e.getKey() == e.ENTER) {
					if(! field.isValid()) { return; }
					saveEndEdit.call(this);
				}
				else if(e.getKey() == e.ESC) {
					endEdit.call(this);
				}
			},this);
			
			// If its a combo then set/save on select
			Field.on('select',function(field) {
				//console.log('AppDV select');
				
				if(field && ! field.isValid()) { return; }
				saveEndEdit.call(this);
			},this);
			
			if(Ext.isFunction(Field.selectText)) {
				// Focus the field and put the cursor at the end
				Field.on('show',function(field){
					field.focus();
					field.setCursorPosition(1000000);
				},this);
			}
			
		}
		/*****************************************************/
		
		// This logic moved into Ext.ux.RapidApp.HtmlEditor
		//if(Field.resizable) {
		//	var resizer = new Ext.Resizable(Field.wrap, {
		//		pinned: true,
		//		handles: 's',
		//		//handles: 's,e,se',
		//		dynamic: true,
		//		listeners : {
		//			'resize' : function(resizable, height, width) {
		//				Field.setSize(height,width);
		//			}
		//		}
		//	});
		//}
		
		Field.show();
		
	},
	
	save_field_data: function(editEl,fieldname,index,Record) {
		if(!editEl.hasClass('editing')) { return false; }
		var Field = this.FieldCmp[index][fieldname];
			
		if(!Field.validate()) { return false; }
		var val = Field.getValue();
		Record.set(fieldname,val);
		
		return true;
	},
	
	cancel_field_editable: function(editEl,fieldname,index,Record) {
	
		var dataWrap = editEl.child('div.data-wrapper');
		var dataEl = editEl.child('div.data-holder');
		var fieldEl = editEl.child('div.field-holder');
		
		if(dataWrap && dataEl && fieldEl) {

			var Fld = this.FieldCmp[index][fieldname];
			if(Fld.contentEl) {
				Fld.contentEl.appendTo(dataWrap);
			}
			Fld.destroy();
			dataEl.setVisible(true);
			
			editEl.removeClass('editing');
		}
		delete this.currentEditingFieldScope;
	},
	
	click_controller: function(dv, index, domNode, event) {
		var target = event.getTarget(null,null,true);
		
		// --- Override for HashNav links (a tags with href starting with '#!/'):
		var href = target.getAttribute('href');
		if(href && target.is('a') && href.search('#!/') === 0) {
			window.location.hash = href;
			return;
		}
		// ---
			
		var domEl = new Ext.Element(domNode);

		// Limit processing to click nodes within this dataview (i.e. not in our submodules)
		var topmostEl = target.findParent('div.appdv-tt-generated.' + dv.id,null,true);
		if(!topmostEl) { 
			// Temporary: map to old function:
			//return Ext.ux.RapidApp.AppDV.click_handler.apply(this,arguments);
			return; 
		}
		var clickableEl = topmostEl.child('div.clickable');
		if(!clickableEl) { return; }

		var Store = this.getStore();
		var Record = Store.getAt(index);
		
		var editEl = clickableEl.child('div.editable-value');
		if(editEl) {
			
			// -- Need this to prevent possible race condition in IE that could
			// cause the click to get processed and then redirect/navigate the 
			// page URL.
			//http://www.sencha.com/forum/showthread.php?81996-Menu-sometimes-redirects-to-a-new-page.&p=393811&viewfull=1#post393811
			// ---- special exception for "filelinks" - if this is a special filelink (rapidapp plugin) 
			//      and we stop the event the download won't happen
			if(!target.hasClass('filelink')) {
				event.stopEvent();
			}
			// --
			
			
			// abort if the Store doesn't have update in its API:
			if(!Store.api.update) { return; } 
			return this.handle_edit_field(target,editEl,Record,index,domEl);
		}
		
		editEl = clickableEl.child('div.edit-record-toggle');
		if(editEl) {
			// abort if the Store doesn't have update in its API and we're not already
			// in edit mode from an Add operation:
			if(!Store.api.update && !domEl.hasClass('editing-record')) { return; } 
			return this.handle_edit_record(target,editEl,Record,index,domEl);
		}
		
		editEl = clickableEl.child('div.delete-record');
		if(editEl) {
			// abort if the Store doesn't have destroy in its API:
			if(!Store.api.destroy) { return; } 
			return this.handle_delete_record(target,editEl,Record,index,domEl);
		}
		editEl = clickableEl.child('div.print-view');
		if(editEl) {
			if(this.printview_url) {
				window.open(this.printview_url,'');
			}
		}
	},
	get_fieldname_by_editEl: function(editEl) {
		var fieldnameEl = editEl.child('div.field-name');
		if(!fieldnameEl) { return false; }
		
		return fieldnameEl.dom.innerHTML;
	},
	handle_delete_record: function (target,editEl,Record,index,domEl) {
		
		// abort if the entire record is in edit mode:
		if(domEl.hasClass('editing-record')) { return; }
		
		// abort if another record is already being updated:
		if(domEl.parent().hasClass('record-update')) { return; }
		
		var Store = this.getStore();
		
		Store.removeRecord(Record);
		//if (!Record.phantom) { Store.saveIfPersist(); }
	},
	handle_edit_field: function (target,editEl,Record,index,domEl) {
		
		// abort if the entire record is in edit mode:
		if(domEl.hasClass('editing-record')) { return; }
		
		// abort if another record is already being updated:
		if(domEl.parent().hasClass('record-update')) { return; }
		
		var Store = this.getStore();
		
		var fieldname = this.get_fieldname_by_editEl(editEl);
		if(!fieldname) { return; }
		
		var dataWrap = editEl.child('div.data-wrapper');
		var dataEl = editEl.child('div.data-holder');
		var fieldEl = editEl.child('div.field-holder');

		if (editEl.hasClass('editing')) {
		
			var Field = this.FieldCmp[index][fieldname];
			
			if(target.hasClass('save')) {
				if(!this.save_field_data(editEl,fieldname,index,Record)) { return; }
				//Store.save();
				Store.saveIfPersist();
			}
			else {
				if(!target.hasClass('cancel')) { return; }
			}
		
			this.cancel_field_editable(editEl,fieldname,index,Record);
		}
		else {
			// require-edit-click is set by "edit-bigfield" to disallow going into edit mode unless the
			// "edit" element itself was clicked:
			if(target.findParent('div.require-edit-click') && !target.hasClass('edit')) { return; }
			this.set_field_editable(editEl,fieldname,index,Record,domEl);
			
		}
		
	},
	beginEditRecord: function(Record) {
		if(Record.editing) { return; }
		Record.beginEdit();
		this.currentEditRecord = Record;
		var Store = this.getStore();
		Store.fireEvent('buttontoggle',Store);
		this.clearSelections();
	},
	endEditRecord: function(Record) {
		if(!Record.editing) { return; }
		Record.endEdit();
		this.currentEditRecord = null;
		var Store = this.getStore();
		Store.fireEvent('buttontoggle',Store);
	},
	simulateEditRecordClick: function(cls,Record,index,editEl) {
		
		if(!Record) { Record = this.currentEditRecord; }
		if(!Record) { return; }
		
		if(!editEl) {
			var domEl = this.getNode(Record);
			editEl = new Ext.Element(domEl);
		}

		var TargetEl = editEl.child(cls);		
		if(typeof index === 'undefined') { index = this.getStore().indexOf(Record); }

		return this.handle_edit_record(TargetEl,editEl,Record,index,editEl);
	},
	simulateSaveClick: function() {
		return this.simulateEditRecordClick('div.save');
	},
	simulateCancelClick: function(Record,index,editEl) {
		return this.simulateEditRecordClick('div.cancel',Record,index,editEl);
	},
	
	handle_edit_record: function (target,editEl,Record,index,domEl) {
		
		var Store = this.getStore();
		
		// New: use the datastore-plus edit record function:
		if(this.use_edit_form && !Record.phantom){
			return Store.editRecordForm(Record);
		}

		var editDoms = domEl.query('div.editable-value');
		var editEls = [];
		Ext.each(editDoms,function(dom) {
			editEls.push(new Ext.Element(dom));
		});
		
		if(domEl.hasClass('editing-record')) {
			
			var save = false;
			if(target.hasClass('save')) {
				save = true;
			}
			else {
				if(!target.hasClass('cancel')) { return; }
			}

			this.beginEditRecord(Record);
		
			var success = true;
			/***** SAVE RECORDS *****/
			Ext.each(editEls,function(editEl) {
				var fieldname = this.get_fieldname_by_editEl(editEl);
				if(!fieldname) { return; }
				
				if(save) {
					if(!this.save_field_data(editEl,fieldname,index,Record)) { 
						success = false;
						return;
					}
				}
			},this);
			
			if(!success) {
				return;
			}
			
			
			/***** REMOVE EDIT STATUS *****/
			Ext.each(editEls,function(editEl) {
				var fieldname = this.get_fieldname_by_editEl(editEl);
				this.cancel_field_editable(editEl,fieldname,index,Record);
			},this);
			
			domEl.removeClass('editing-record');	
			domEl.parent().removeClass('record-update');
			//Record.endEdit();
			this.endEditRecord(Record);
			
			if(Record.phantom && !save) {
				return Store.remove(Record);
			}
			
			//this.scrollBottomToolbarIntoView.defer(100,this);
			if (this.isSaving) { return; }
			
			// persist_on_add is AppDV specific, and causes a store save to happen *after* a
			// new record has been added via filling out fields. when persist_immediately.create
			// is set empty records are instantly created without giving the user the chance
			// set the initial values
			if(Record.phantom && this.persist_on_add) { return Store.save(); }
			
			return Store.saveIfPersist();
		}
		else {
			// abort if another record is already being updated:
			if(domEl.parent().hasClass('record-update')) { return; }

			domEl.parent().addClass('record-update');
			domEl.addClass('editing-record');
			
			Ext.each(editEls,function(editEl) {
				var fieldname = this.get_fieldname_by_editEl(editEl);
				this.set_field_editable(editEl,fieldname,index,Record);
			},this);

			this.beginEditRecord(Record);
		}
	},
	
	scrollBottomToolbarIntoView: function(){
		var node = this.getParentScrollNode(this.getEl().dom);
		if(!node) { return; }
		Ext.fly(this.ownerCt.getBottomToolbar().getEl()).scrollIntoView(node);
	},
	
	scrollRecordIntoView: function(Record) {
		if(!this.getStore()) { return; }
		
		if(Record == Record.store.getLastRecord()) {
			return this.scrollBottomToolbarIntoView();
		}
		
		var node = this.getParentScrollNode(this.getEl().dom);
		if(!node) { return; }
		Ext.fly(this.getNode(Record)).scrollIntoView(node);

	},
	
	getParentScrollNode: function(node) {
		if(!node || !node.style) { return null; }
		if(node.style.overflow == 'auto') { return node; }
		if(node.parentNode) { return this.getParentScrollNode(node.parentNode); }
		return null;
	}
});
Ext.reg('appdv', Ext.ux.RapidApp.AppDV.DataView);

Ext.ns('Ext.ux.ErrorTrace');
Ext.ux.ErrorTrace.toggleThing= function (ref, type, hideMsg, showMsg) {
 var css = document.getElementById(type+'-'+ref).style;
 css.display = css.display == 'block' ? 'none' : 'block';

 var hyperlink = document.getElementById('toggle-'+ref);
 hyperlink.textContent = css.display == 'block' ? hideMsg : showMsg;
}

Ext.ux.ErrorTrace.toggleArguments= function (ref) {
 toggleThing(ref, 'arguments', 'Hide function arguments', 'Show function arguments');
}

Ext.ux.ErrorTrace.toggleLexicals= function (ref) {
 toggleThing(ref, 'lexicals', 'Hide lexical variables', 'Show lexical variables');
}

Ext.ns('Ext.ux.RapidApp');

Ext.ux.RapidApp.genericChangePW = function(username,post_url) {

	// Shouldn't come up, but check for and close existing windows:
	var winId = 'general-change-pw-window';
	var curWin = Ext.getCmp(winId);
	if(curWin){ curWin.close(); }
	
	var win;
	
	var newPwField = new Ext.form.TextField({
		name: 'new_pw',
		inputType: 'password',
		fieldLabel: 'New Password',
		allowBlank: false
	});
	
	var oldPwField = new Ext.form.TextField({
		name: 'current_pw',
		inputType: 'password',
		fieldLabel: 'Current Password',
		allowBlank: false
	});
	
	var success_fn = function(res) {
		// Check for special text in response body:
		if(res && res.responseText && res.responseText == 'bad_old_pw'){
			win.hide_mask();
			return Ext.Msg.alert('Bad Password', 'Current password incorrect');
		}

		win.close();
		Ext.Msg.alert('Success', 'Password Changed Successfully');
	};
	
	var failure_fn = function() {
		win.hide_mask();
		// Don't show a message; assume the backend set a RapidApp exception:
		//Ext.Msg.alert('Failed', 'Failed to change password');
	};
	
	var doChange = function() {
		win.show_mask();
		Ext.Ajax.request({
			url: post_url,
			method: 'POST',
			params: { 
				username: username, 
				old_pw: oldPwField.getValue(),
				new_pw: newPwField.getValue()
			},
			success: success_fn,
			failure: failure_fn
		});
	};
	
	var fp = new Ext.form.FormPanel({
		xtype: 'form',
		frame: true,
		labelAlign: 'right',
		
		//plugins: ['dynamic-label-width'],
		labelWidth: 160,
		labelPad: 15,
		bodyStyle: 'padding: 10px 25px 5px 10px;',
		defaults: { anchor: '-0' },
		autoScroll: true,
		monitorValid: true,
		buttonAlign: 'right',
		minButtonWidth: 100,
		
		items: [
			{ html: '<div class="ra-change-pw-heading">' +
					'<div>Change&nbsp;Password</div>' +
					'<div class="sub">Username:&nbsp;&nbsp;<span class="blue-text-code">' + 
						username + 
					'</span></div>' +
				'</div>'
			},
			{ xtype: 'spacer', height: 10 },
			
			oldPwField,
			newPwField,
			{
				name: 'confirm_pw',
				xtype: 'textfield',
				inputType: 'password',
				fieldLabel: 'Confirm New Password',
				allowBlank: false,
				validator: function(v) {
					if(v != newPwField.getValue()) {
						return 'Passwords do not match';
					}
					return true;
				}
			}
		],
		
		buttons: [
			{
				name: 'ok',
				text: 'Ok',
				iconCls: 'ra-icon-key',
				formBind: true,
				handler: doChange
			},
			{
				name: 'cancel',
				text: 'Cancel',
				handler: function(btn) {
					win.close();
				},
				scope: this
			}
		]
	});
	
	

	win = new Ext.Window({
		id: winId, 
		title: 'Change Password (' + username + ')',
		layout: 'fit',
		width: 475,
		height: 350,
		minWidth: 455,
		minHeight: 350,
		closable: true,
		closeAction: 'close',
		modal: true,
		items: fp,
		show_mask: function() { win.myMask.show(); },
		hide_mask: function() { win.myMask.hide(); },
		listeners: {
			afterrender: function() {
				var El = win.getEl();
				// Create the actual mask object tied to the window
				win.myMask = new Ext.LoadMask(El, {msg:"Please wait..."});
				
				new Ext.KeyMap(El, {
					key: Ext.EventObject.ENTER,
					shift: false,
					alt: false,
					fn: function(keyCode, e){
						fp.el.select('button').item(0).dom.click();
					}
				});
			},
			show: function(){
				oldPwField.focus('',10); 
				oldPwField.focus('',100); 
				oldPwField.focus('',300);
			}
		}
	});
	
	win.show();
};
// moment.js
// version : 1.7.2
// author : Tim Wood
// license : MIT
// momentjs.com
(function(a){function E(a,b,c,d){var e=c.lang();return e[a].call?e[a](c,d):e[a][b]}function F(a,b){return function(c){return K(a.call(this,c),b)}}function G(a){return function(b){var c=a.call(this,b);return c+this.lang().ordinal(c)}}function H(a,b,c){this._d=a,this._isUTC=!!b,this._a=a._a||null,this._lang=c||!1}function I(a){var b=this._data={},c=a.years||a.y||0,d=a.months||a.M||0,e=a.weeks||a.w||0,f=a.days||a.d||0,g=a.hours||a.h||0,h=a.minutes||a.m||0,i=a.seconds||a.s||0,j=a.milliseconds||a.ms||0;this._milliseconds=j+i*1e3+h*6e4+g*36e5,this._days=f+e*7,this._months=d+c*12,b.milliseconds=j%1e3,i+=J(j/1e3),b.seconds=i%60,h+=J(i/60),b.minutes=h%60,g+=J(h/60),b.hours=g%24,f+=J(g/24),f+=e*7,b.days=f%30,d+=J(f/30),b.months=d%12,c+=J(d/12),b.years=c,this._lang=!1}function J(a){return a<0?Math.ceil(a):Math.floor(a)}function K(a,b){var c=a+"";while(c.length<b)c="0"+c;return c}function L(a,b,c){var d=b._milliseconds,e=b._days,f=b._months,g;d&&a._d.setTime(+a+d*c),e&&a.date(a.date()+e*c),f&&(g=a.date(),a.date(1).month(a.month()+f*c).date(Math.min(g,a.daysInMonth())))}function M(a){return Object.prototype.toString.call(a)==="[object Array]"}function N(a,b){var c=Math.min(a.length,b.length),d=Math.abs(a.length-b.length),e=0,f;for(f=0;f<c;f++)~~a[f]!==~~b[f]&&e++;return e+d}function O(a,b,c,d){var e,f,g=[];for(e=0;e<7;e++)g[e]=a[e]=a[e]==null?e===2?1:0:a[e];return a[7]=g[7]=b,a[8]!=null&&(g[8]=a[8]),a[3]+=c||0,a[4]+=d||0,f=new Date(0),b?(f.setUTCFullYear(a[0],a[1],a[2]),f.setUTCHours(a[3],a[4],a[5],a[6])):(f.setFullYear(a[0],a[1],a[2]),f.setHours(a[3],a[4],a[5],a[6])),f._a=g,f}function P(a,c){var d,e,g=[];!c&&h&&(c=require("./lang/"+a));for(d=0;d<i.length;d++)c[i[d]]=c[i[d]]||f.en[i[d]];for(d=0;d<12;d++)e=b([2e3,d]),g[d]=new RegExp("^"+(c.months[d]||c.months(e,""))+"|^"+(c.monthsShort[d]||c.monthsShort(e,"")).replace(".",""),"i");return c.monthsParse=c.monthsParse||g,f[a]=c,c}function Q(a){var c=typeof a=="string"&&a||a&&a._lang||null;return c?f[c]||P(c):b}function R(a){return a.match(/\[.*\]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function S(a){var b=a.match(k),c,d;for(c=0,d=b.length;c<d;c++)D[b[c]]?b[c]=D[b[c]]:b[c]=R(b[c]);return function(e){var f="";for(c=0;c<d;c++)f+=typeof b[c].call=="function"?b[c].call(e,a):b[c];return f}}function T(a,b){function d(b){return a.lang().longDateFormat[b]||b}var c=5;while(c--&&l.test(b))b=b.replace(l,d);return A[b]||(A[b]=S(b)),A[b](a)}function U(a){switch(a){case"DDDD":return p;case"YYYY":return q;case"S":case"SS":case"SSS":case"DDD":return o;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":case"a":case"A":return r;case"Z":case"ZZ":return s;case"T":return t;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return n;default:return new RegExp(a.replace("\\",""))}}function V(a,b,c,d){var e,f;switch(a){case"M":case"MM":c[1]=b==null?0:~~b-1;break;case"MMM":case"MMMM":for(e=0;e<12;e++)if(Q().monthsParse[e].test(b)){c[1]=e,f=!0;break}f||(c[8]=!1);break;case"D":case"DD":case"DDD":case"DDDD":b!=null&&(c[2]=~~b);break;case"YY":c[0]=~~b+(~~b>70?1900:2e3);break;case"YYYY":c[0]=~~Math.abs(b);break;case"a":case"A":d.isPm=(b+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":c[3]=~~b;break;case"m":case"mm":c[4]=~~b;break;case"s":case"ss":c[5]=~~b;break;case"S":case"SS":case"SSS":c[6]=~~(("0."+b)*1e3);break;case"Z":case"ZZ":d.isUTC=!0,e=(b+"").match(x),e&&e[1]&&(d.tzh=~~e[1]),e&&e[2]&&(d.tzm=~~e[2]),e&&e[0]==="+"&&(d.tzh=-d.tzh,d.tzm=-d.tzm)}b==null&&(c[8]=!1)}function W(a,b){var c=[0,0,1,0,0,0,0],d={tzh:0,tzm:0},e=b.match(k),f,g;for(f=0;f<e.length;f++)g=(U(e[f]).exec(a)||[])[0],g&&(a=a.slice(a.indexOf(g)+g.length)),D[e[f]]&&V(e[f],g,c,d);return d.isPm&&c[3]<12&&(c[3]+=12),d.isPm===!1&&c[3]===12&&(c[3]=0),O(c,d.isUTC,d.tzh,d.tzm)}function X(a,b){var c,d=a.match(m)||[],e,f=99,g,h,i;for(g=0;g<b.length;g++)h=W(a,b[g]),e=T(new H(h),b[g]).match(m)||[],i=N(d,e),i<f&&(f=i,c=h);return c}function Y(a){var b="YYYY-MM-DDT",c;if(u.exec(a)){for(c=0;c<4;c++)if(w[c][1].exec(a)){b+=w[c][0];break}return s.exec(a)?W(a,b+" Z"):W(a,b)}return new Date(a)}function Z(a,b,c,d,e){var f=e.relativeTime[a];return typeof f=="function"?f(b||1,!!c,a,d):f.replace(/%d/i,b||1)}function $(a,b,c){var e=d(Math.abs(a)/1e3),f=d(e/60),g=d(f/60),h=d(g/24),i=d(h/365),j=e<45&&["s",e]||f===1&&["m"]||f<45&&["mm",f]||g===1&&["h"]||g<22&&["hh",g]||h===1&&["d"]||h<=25&&["dd",h]||h<=45&&["M"]||h<345&&["MM",d(h/30)]||i===1&&["y"]||["yy",i];return j[2]=b,j[3]=a>0,j[4]=c,Z.apply({},j)}function _(a,c){b.fn[a]=function(a){var b=this._isUTC?"UTC":"";return a!=null?(this._d["set"+b+c](a),this):this._d["get"+b+c]()}}function ab(a){b.duration.fn[a]=function(){return this._data[a]}}function bb(a,c){b.duration.fn["as"+a]=function(){return+this/c}}var b,c="1.7.2",d=Math.round,e,f={},g="en",h=typeof module!="undefined"&&module.exports,i="months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem".split("|"),j=/^\/?Date\((\-?\d+)/i,k=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|.)/g,l=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?)/g,m=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,n=/\d\d?/,o=/\d{1,3}/,p=/\d{3}/,q=/\d{1,4}/,r=/[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i,s=/Z|[\+\-]\d\d:?\d\d/i,t=/T/i,u=/^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,v="YYYY-MM-DDTHH:mm:ssZ",w=[["HH:mm:ss.S",/T\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/T\d\d:\d\d:\d\d/],["HH:mm",/T\d\d:\d\d/],["HH",/T\d\d/]],x=/([\+\-]|\d\d)/gi,y="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),z={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},A={},B="DDD w M D d".split(" "),C="M D H h m s w".split(" "),D={M:function(){return this.month()+1},MMM:function(a){return E("monthsShort",this.month(),this,a)},MMMM:function(a){return E("months",this.month(),this,a)},D:function(){return this.date()},DDD:function(){var a=new Date(this.year(),this.month(),this.date()),b=new Date(this.year(),0,1);return~~((a-b)/864e5+1.5)},d:function(){return this.day()},dd:function(a){return E("weekdaysMin",this.day(),this,a)},ddd:function(a){return E("weekdaysShort",this.day(),this,a)},dddd:function(a){return E("weekdays",this.day(),this,a)},w:function(){var a=new Date(this.year(),this.month(),this.date()-this.day()+5),b=new Date(a.getFullYear(),0,4);return~~((a-b)/864e5/7+1.5)},YY:function(){return K(this.year()%100,2)},YYYY:function(){return K(this.year(),4)},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return K(~~(this.milliseconds()/10),2)},SSS:function(){return K(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return a<0&&(a=-a,b="-"),b+K(~~(a/60),2)+":"+K(~~a%60,2)},ZZ:function(){var a=-this.zone(),b="+";return a<0&&(a=-a,b="-"),b+K(~~(10*a/6),4)}};while(B.length)e=B.pop(),D[e+"o"]=G(D[e]);while(C.length)e=C.pop(),D[e+e]=F(D[e],2);D.DDDD=F(D.DDD,3),b=function(c,d){if(c===null||c==="")return null;var e,f;return b.isMoment(c)?new H(new Date(+c._d),c._isUTC,c._lang):(d?M(d)?e=X(c,d):e=W(c,d):(f=j.exec(c),e=c===a?new Date:f?new Date(+f[1]):c instanceof Date?c:M(c)?O(c):typeof c=="string"?Y(c):new Date(c)),new H(e))},b.utc=function(a,c){return M(a)?new H(O(a,!0),!0):(typeof a=="string"&&!s.exec(a)&&(a+=" +0000",c&&(c+=" Z")),b(a,c).utc())},b.unix=function(a){return b(a*1e3)},b.duration=function(a,c){var d=b.isDuration(a),e=typeof a=="number",f=d?a._data:e?{}:a,g;return e&&(c?f[c]=a:f.milliseconds=a),g=new I(f),d&&(g._lang=a._lang),g},b.humanizeDuration=function(a,c,d){return b.duration(a,c===!0?null:c).humanize(c===!0?!0:d)},b.version=c,b.defaultFormat=v,b.lang=function(a,c){var d;if(!a)return g;(c||!f[a])&&P(a,c);if(f[a]){for(d=0;d<i.length;d++)b[i[d]]=f[a][i[d]];b.monthsParse=f[a].monthsParse,g=a}},b.langData=Q,b.isMoment=function(a){return a instanceof H},b.isDuration=function(a){return a instanceof I},b.lang("en",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(a){var b=a%10;return~~(a%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th"}}),b.fn=H.prototype={clone:function(){return b(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this._d.toString()},toDate:function(){return this._d},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds(),!!this._isUTC]},isValid:function(){return this._a?this._a[8]!=null?!!this._a[8]:!N(this._a,(this._a[7]?b.utc(this._a):b(this._a)).toArray()):!isNaN(this._d.getTime())},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(a){return T(this,a?a:b.defaultFormat)},add:function(a,c){var d=c?b.duration(+c,a):b.duration(a);return L(this,d,1),this},subtract:function(a,c){var d=c?b.duration(+c,a):b.duration(a);return L(this,d,-1),this},diff:function(a,c,e){var f=this._isUTC?b(a).utc():b(a).local(),g=(this.zone()-f.zone())*6e4,h=this._d-f._d-g,i=this.year()-f.year(),j=this.month()-f.month(),k=this.date()-f.date(),l;return c==="months"?l=i*12+j+k/30:c==="years"?l=i+(j+k/30)/12:l=c==="seconds"?h/1e3:c==="minutes"?h/6e4:c==="hours"?h/36e5:c==="days"?h/864e5:c==="weeks"?h/6048e5:h,e?l:d(l)},from:function(a,c){return b.duration(this.diff(a)).lang(this._lang).humanize(!c)},fromNow:function(a){return this.from(b(),a)},calendar:function(){var a=this.diff(b().sod(),"days",!0),c=this.lang().calendar,d=c.sameElse,e=a<-6?d:a<-1?c.lastWeek:a<0?c.lastDay:a<1?c.sameDay:a<2?c.nextDay:a<7?c.nextWeek:d;return this.format(typeof e=="function"?e.apply(this):e)},isLeapYear:function(){var a=this.year();return a%4===0&&a%100!==0||a%400===0},isDST:function(){return this.zone()<b([this.year()]).zone()||this.zone()<b([this.year(),5]).zone()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return a==null?b:this.add({d:a-b})},startOf:function(a){switch(a.replace(/s$/,"")){case"year":this.month(0);case"month":this.date(1);case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return this},endOf:function(a){return this.startOf(a).add(a.replace(/s?$/,"s"),1).subtract("ms",1)},sod:function(){return this.clone().startOf("day")},eod:function(){return this.clone().endOf("day")},zone:function(){return this._isUTC?0:this._d.getTimezoneOffset()},daysInMonth:function(){return b.utc([this.year(),this.month()+1,0]).date()},lang:function(b){return b===a?Q(this):(this._lang=b,this)}};for(e=0;e<y.length;e++)_(y[e].toLowerCase(),y[e]);_("year","FullYear"),b.duration.fn=I.prototype={weeks:function(){return J(this.days()/7)},valueOf:function(){return this._milliseconds+this._days*864e5+this._months*2592e6},humanize:function(a){var b=+this,c=this.lang().relativeTime,d=$(b,!a,this.lang()),e=b<=0?c.past:c.future;return a&&(typeof e=="function"?d=e(d):d=e.replace(/%s/i,d)),d},lang:b.fn.lang};for(e in z)z.hasOwnProperty(e)&&(bb(e,z[e]),ab(e.toLowerCase()));bb("Weeks",6048e5),h&&(module.exports=b),typeof ender=="undefined"&&(this.moment=b),typeof define=="function"&&define.amd&&define("moment",[],function(){return b})}).call(this);
