Ext.apply(Ext, {
			isString : function(e) {
				return typeof e === "string"
			},

			copyTo : function(dest, source, names) {
				if (typeof names == 'string') {
					names = names.split(/[,;\s]/);
				}
				Ext.each(names, function(name) {
							if (source.hasOwnProperty(name)) {
								dest[name] = source[name];
							}
						}, this);
				return dest;
			},

			isDefined : function(v) {
				return typeof v !== 'undefined';
			},

			toArray : function() {
				return Ext.isIE ? function(a, i, j, res) {
					res = [];
					for (var x = 0, len = a.length; x < len; x++) {
						res.push(a[x]);
					}
					return res.slice(i || 0, j || res.length);
				} : function(a, i, j) {
					return Array.prototype.slice.call(a, i || 0, j || a.length);
				}
			}(),

			isObject : function(f) {
				if (typeof f == "object")
					return true;
				return false;
			}
		});

Ext.util.Format = function() {
	var trimRe = /^\s+|\s+$/g, stripTagsRE = /<\/?[^>]+>/gi, stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, nl2brRe = /\r?\n/g;

	return {
		/**
		 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
		 * 
		 * @param {String}
		 *            value The string to truncate
		 * @param {Number}
		 *            length The maximum length to allow before truncating
		 * @param {Boolean}
		 *            word True to try to find a common work break
		 * @return {String} The converted text
		 */
		ellipsis : function(value, len, word) {
			if (value && value.length > len) {
				if (word) {
					var vs = value.substr(0, len - 2), index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
					if (index == -1 || index < (len - 15)) {
						return value.substr(0, len - 3) + "...";
					} else {
						return vs.substr(0, index) + "...";
					}
				} else {
					return value.substr(0, len - 3) + "...";
				}
			}
			return value;
		},

		/**
		 * Checks a reference and converts it to empty string if it is undefined
		 * 
		 * @param {Mixed}
		 *            value Reference to check
		 * @return {Mixed} Empty string if converted, otherwise the original value
		 */
		undef : function(value) {
			return value !== undefined ? value : "";
		},

		/**
		 * Checks a reference and converts it to the default value if it's empty
		 * 
		 * @param {Mixed}
		 *            value Reference to check
		 * @param {String}
		 *            defaultValue The value to insert of it's undefined (defaults to "")
		 * @return {String}
		 */
		defaultValue : function(value, defaultValue) {
			return value !== undefined && value !== '' ? value : defaultValue;
		},

		/**
		 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
		 * 
		 * @param {String}
		 *            value The string to encode
		 * @return {String} The encoded text
		 */
		htmlEncode : function(value) {
			return !value ? value : String(value).replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
		},

		/**
		 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
		 * 
		 * @param {String}
		 *            value The string to decode
		 * @return {String} The decoded text
		 */
		htmlDecode : function(value) {
			return !value ? value : String(value).replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
		},

		/**
		 * Trims any whitespace from either side of a string
		 * 
		 * @param {String}
		 *            value The text to trim
		 * @return {String} The trimmed text
		 */
		trim : function(value) {
			return String(value).replace(trimRe, "");
		},

		/**
		 * Returns a substring from within an original string
		 * 
		 * @param {String}
		 *            value The original text
		 * @param {Number}
		 *            start The start index of the substring
		 * @param {Number}
		 *            length The length of the substring
		 * @return {String} The substring
		 */
		substr : function(value, start, length) {
			return String(value).substr(start, length);
		},

		/**
		 * Converts a string to all lower case letters
		 * 
		 * @param {String}
		 *            value The text to convert
		 * @return {String} The converted text
		 */
		lowercase : function(value) {
			return String(value).toLowerCase();
		},

		/**
		 * Converts a string to all upper case letters
		 * 
		 * @param {String}
		 *            value The text to convert
		 * @return {String} The converted text
		 */
		uppercase : function(value) {
			return String(value).toUpperCase();
		},

		/**
		 * Converts the first character only of a string to upper case
		 * 
		 * @param {String}
		 *            value The text to convert
		 * @return {String} The converted text
		 */
		capitalize : function(value) {
			return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
		},

		// private
		call : function(value, fn) {
			if (arguments.length > 2) {
				var args = Array.prototype.slice.call(arguments, 2);
				args.unshift(value);
				return eval(fn).apply(window, args);
			} else {
				return eval(fn).call(window, value);
			}
		},

		/**
		 * Format a number as US currency
		 * 
		 * @param {Number/String}
		 *            value The numeric value to format
		 * @return {String} The formatted currency string
		 */
		usMoney : function(v) {
			v = (Math.round((v - 0) * 100)) / 100;
			v = (v == Math.floor(v)) ? v + ".00" : ((v * 10 == Math.floor(v * 10)) ? v + "0" : v);
			v = String(v);
			var ps = v.split('.'), whole = ps[0], sub = ps[1] ? '.' + ps[1] : '.00', r = /(\d+)(\d{3})/;
			while (r.test(whole)) {
				whole = whole.replace(r, '$1' + ',' + '$2');
			}
			v = whole + sub;
			if (v.charAt(0) == '-') {
				return '-$' + v.substr(1);
			}
			return "$" + v;
		},

		/**
		 * Parse a value into a formatted date using the specified format pattern.
		 * 
		 * @param {String/Date}
		 *            value The value to format (Strings must conform to the format expected by the javascript Date object's <a
		 *            href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method)
		 * @param {String}
		 *            format (optional) Any valid date format string (defaults to 'm/d/Y')
		 * @return {String} The formatted date string
		 */
		date : function(v, format) {
			if (!v) {
				return "";
			}
			if (!Ext.isDate(v)) {
				v = new Date(Date.parse(v));
			}
			return v.dateFormat(format || "m/d/Y");
		},

		/**
		 * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
		 * 
		 * @param {String}
		 *            format Any valid date format string
		 * @return {Function} The date formatting function
		 */
		dateRenderer : function(format) {
			return function(v) {
				return Ext.util.Format.date(v, format);
			};
		},

		/**
		 * Strips all HTML tags
		 * 
		 * @param {Mixed}
		 *            value The text from which to strip tags
		 * @return {String} The stripped text
		 */
		stripTags : function(v) {
			return !v ? v : String(v).replace(stripTagsRE, "");
		},

		/**
		 * Strips all script tags
		 * 
		 * @param {Mixed}
		 *            value The text from which to strip script tags
		 * @return {String} The stripped text
		 */
		stripScripts : function(v) {
			return !v ? v : String(v).replace(stripScriptsRe, "");
		},

		/**
		 * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
		 * 
		 * @param {Number/String}
		 *            size The numeric value to format
		 * @return {String} The formatted file size
		 */
		fileSize : function(size) {
			if (size < 1024) {
				return size + " bytes";
			} else if (size < 1048576) {
				return (Math.round(((size * 10) / 1024)) / 10) + " KB";
			} else {
				return (Math.round(((size * 10) / 1048576)) / 10) + " MB";
			}
		},

		/**
		 * It does simple math for use in a template, for example:
		 * 
		 * <pre><code>
		 * var tpl = new Ext.Template('{value} * 10 = {value:math(&quot;* 10&quot;)}');
		 * </code></pre>
		 * 
		 * @return {Function} A function that operates on the passed value.
		 */
		math : function() {
			var fns = {};
			return function(v, a) {
				if (!fns[a]) {
					fns[a] = new Function('v', 'return v ' + a + ';');
				}
				return fns[a](v);
			}
		}(),

		/**
		 * Rounds the passed number to the required decimal precision.
		 * 
		 * @param {Number/String}
		 *            value The numeric value to round.
		 * @param {Number}
		 *            precision The number of decimal places to which to round the first parameter's value.
		 * @return {Number} The rounded value.
		 */
		round : function(value, precision) {
			var result = Number(value);
			if (typeof precision == 'number') {
				precision = Math.pow(10, precision);
				result = Math.round(value * precision) / precision;
			}
			return result;
		},

		/**
		 * Formats the number according to the format string. <div style="margin-left:40px">examples (123456.789): <div style="margin-left:10px"> 0 - (123456) show only digits, no
		 * precision<br>
		 * 0.00 - (123456.78) show only digits, 2 precision<br>
		 * 0.0000 - (123456.7890) show only digits, 4 precision<br>
		 * 0,000 - (123,456) show comma and digits, no precision<br>
		 * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
		 * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
		 * To reverse the grouping (,) and decimal (.) for international numbers, add /i to the end. For example: 0.000,00/i </div></div>
		 * 
		 * @param {Number}
		 *            v The number to format.
		 * @param {String}
		 *            format The way you would like to format this text.
		 * @return {String} The formatted number.
		 */
		number : function(v, format) {
			if (!format) {
				return v;
			}
			v = Ext.num(v, NaN);
			if (isNaN(v)) {
				return '';
			}
			var comma = ',', dec = '.', i18n = false, neg = v < 0;

			v = Math.abs(v);
			if (format.substr(format.length - 2) == '/i') {
				format = format.substr(0, format.length - 2);
				i18n = true;
				comma = '.';
				dec = ',';
			}

			var hasComma = format.indexOf(comma) != -1, psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);

			if (1 < psplit.length) {
				v = v.toFixed(psplit[1].length);
			} else if (2 < psplit.length) {
				throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
			} else {
				v = v.toFixed(0);
			}

			var fnum = v.toString();

			psplit = fnum.split('.');

			if (hasComma) {
				var cnum = psplit[0], parr = [], j = cnum.length, m = Math.floor(j / 3), n = cnum.length % 3 || 3;

				for (var i = 0; i < j; i += n) {
					if (i != 0) {
						n = 3;
					}
					parr[parr.length] = cnum.substr(i, n);
					m -= 1;
				}
				fnum = parr.join(comma);
				if (psplit[1]) {
					fnum += dec + psplit[1];
				}
			} else {
				if (psplit[1]) {
					fnum = psplit[0] + dec + psplit[1];
				}
			}

			return (neg ? '-' : '') + format.replace(/[\d,?\.?]+/, fnum);
		},

		/**
		 * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
		 * 
		 * @param {String}
		 *            format Any valid number format string for {@link #number}
		 * @return {Function} The number formatting function
		 */
		numberRenderer : function(format) {
			return function(v) {
				return Ext.util.Format.number(v, format);
			};
		},

		/**
		 * Selectively do a plural form of a word based on a numeric value. For example, in a template, {commentCount:plural("Comment")} would result in "1 Comment" if commentCount
		 * was 1 or would be "x Comments" if the value is 0 or greater than 1.
		 * 
		 * @param {Number}
		 *            value The value to compare against
		 * @param {String}
		 *            singular The singular form of the word
		 * @param {String}
		 *            plural (optional) The plural form of the word (defaults to the singular with an "s")
		 */
		plural : function(v, s, p) {
			return v + ' ' + (v == 1 ? s : (p ? p : s + 's'));
		},

		/**
		 * Converts newline characters to the HTML tag &lt;br/>
		 * 
		 * @param {String}
		 *            The string value to format.
		 * @return {String} The string with embedded &lt;br/> tags in place of newlines.
		 */
		nl2br : function(v) {
			return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
		}
	}
}();

Ext.ns('Ext.slider');
Ext.slider.Tip = Ext.extend(Ext.Tip, {
			minWidth : 10,
			offsets : [0, -10],

			init : function(slider) {
				slider.on({
							scope : this,
							dragstart : this.onSlide,
							drag : this.onSlide,
							dragend : this.hide,
							destroy : this.destroy
						});
			},

			/**
			 * @private Called whenever a dragstart or drag event is received on the associated Thumb. Aligns the Tip with the Thumb's new position.
			 * @param {Ext.slider.MultiSlider}
			 *            slider The slider
			 * @param {Ext.EventObject}
			 *            e The Event object
			 * @param {Ext.slider.Thumb}
			 *            thumb The thumb that the Tip is attached to
			 */
			onSlide : function(slider, e, thumb) {
				this.show();
				this.body.update(this.getText(thumb));
				this.doAutoWidth();
				this.el.alignTo(thumb.el, 'b-t?', this.offsets);
			},

			/**
			 * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider Thumb that the Tip is attached to. Override to
			 * customize.
			 * 
			 * @param {Ext.slider.Thumb}
			 *            thumb The Thumb that the Tip is attached to
			 * @return {String} The text to display in the tip
			 */
			getText : function(thumb) {
				return String(thumb.value);
			}
		});

// backwards compatibility - SliderTip used to be a ux before 3.2
Ext.ux.SliderTip = Ext.slider.Tip;

/**
 * @class Ext.slider.Thumb
 * @extends Object Represents a single thumb element on a Slider. This would not usually be created manually and would instead be created internally by an
 *          {@link Ext.slider.MultiSlider Ext.Slider}.
 */
Ext.slider.Thumb = Ext.extend(Object, {

			/**
			 * @constructor
			 * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required)
			 */
			constructor : function(config) {
				/**
				 * @property slider
				 * @type Ext.slider.MultiSlider The slider this thumb is contained within
				 */
				Ext.apply(this, config || {}, {
							cls : 'x-slider-thumb',

							/**
							 * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
							 */
							constrain : false
						});

				Ext.slider.Thumb.superclass.constructor.call(this, config);

				if (this.slider.vertical) {
					Ext.apply(this, Ext.slider.Thumb.Vertical);
				}
			},

			/**
			 * Renders the thumb into a slider
			 */
			render : function() {
				this.el = this.slider.innerEl.insertFirst({
							cls : this.cls
						});

				this.initEvents();
			},

			/**
			 * Enables the thumb if it is currently disabled
			 */
			enable : function() {
				this.disabled = false;
				this.el.removeClass(this.slider.disabledClass);
			},

			/**
			 * Disables the thumb if it is currently enabled
			 */
			disable : function() {
				this.disabled = true;
				this.el.addClass(this.slider.disabledClass);
			},

			/**
			 * Sets up an Ext.dd.DragTracker for this thumb
			 */
			initEvents : function() {
				var el = this.el;

				el.addClassOnOver('x-slider-thumb-over');

				this.tracker = new Ext.dd.DragTracker({
							onBeforeStart : this.onBeforeDragStart.createDelegate(this),
							onStart : this.onDragStart.createDelegate(this),
							onDrag : this.onDrag.createDelegate(this),
							onEnd : this.onDragEnd.createDelegate(this),
							tolerance : 3,
							autoStart : 300
						});

				this.tracker.initEl(el);
			},

			/**
			 * @private This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled, this returns false to disable the DragTracker too.
			 * @return {Boolean} False if the slider is currently disabled
			 */
			onBeforeDragStart : function(e) {
				if (this.disabled) {
					return false;
				} else {
					this.slider.promoteThumb(this);
					return true;
				}
			},

			/**
			 * @private This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class to the thumb and fires the 'dragstart' event
			 */
			onDragStart : function(e) {
				this.el.addClass('x-slider-thumb-drag');
				this.dragging = true;
				this.dragStartValue = this.value;

				this.slider.fireEvent('dragstart', this.slider, e, this);
			},

			/**
			 * @private This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time the DragTracker detects a drag movement. It updates
			 *          the Slider's value using the position of the drag
			 */
			onDrag : function(e) {
				var slider = this.slider, index = this.index, newValue = this.getNewValue();

				if (this.constrain) {
					var above = slider.thumbs[index + 1], below = slider.thumbs[index - 1];

					if (below != undefined && newValue <= below.value)
						newValue = below.value;
					if (above != undefined && newValue >= above.value)
						newValue = above.value;
				}

				slider.setValue(index, newValue, false);
				slider.fireEvent('drag', slider, e, this);
			},

			getNewValue : function() {
				var slider = this.slider, pos = slider.innerEl.translatePoints(this.tracker.getXY());

				return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
			},

			/**
			 * @private This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and fires the 'changecomplete' event with the new value
			 */
			onDragEnd : function(e) {
				var slider = this.slider, value = this.value;

				this.el.removeClass('x-slider-thumb-drag');

				this.dragging = false;
				slider.fireEvent('dragend', slider, e);

				if (this.dragStartValue != value) {
					slider.fireEvent('changecomplete', slider, value, this);
				}
			}
		});

/**
 * @class Ext.slider.MultiSlider
 * @extends Ext.BoxComponent Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking and animation. Can be added as an
 *          item to any container. Example usage:
 * 
 * <pre>
 * new Ext.Slider({
 * 			renderTo : Ext.getBody(),
 * 			width : 200,
 * 			value : 50,
 * 			increment : 10,
 * 			minValue : 0,
 * 			maxValue : 100
 * 		});
 * </pre>
 * 
 * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
 * 
 * <pre>
 * 	 new Ext.Slider({
 * 	 renderTo: Ext.getBody(),
 * 	 width: 200,
 * 	 values: [25, 50, 75],
 * 	 minValue: 0,
 * 	 maxValue: 100,
 * 
 * 	 //this defaults to true, setting to false allows the thumbs to pass each other
 * 	 {@link #constrainThumbs}: false
 * 	 });
 * 	
 * </pre>
 */
Ext.slider.MultiSlider = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {Number} value The value to initialize the slider with. Defaults to minValue.
	 */
	/**
	 * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
	 */
	vertical : false,
	/**
	 * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
	 */
	minValue : 0,
	/**
	 * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
	 */
	maxValue : 100,
	/**
	 * @cfg {Number/Boolean} decimalPrecision.
	 *      <p>
	 *      The number of decimal places to which to round the Slider's value. Defaults to 0.
	 *      </p>
	 *      <p>
	 *      To disable rounding, configure as <tt><b>false</b></tt>.
	 *      </p>
	 */
	decimalPrecision : 0,
	/**
	 * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used
	 *      instead.
	 */
	keyIncrement : 1,
	/**
	 * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
	 */
	increment : 0,

	/**
	 * @private
	 * @property clickRange
	 * @type Array Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom], the
	 *       click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top' value of the click
	 *       event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range
	 */
	clickRange : [5, 15],

	/**
	 * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
	 */
	clickToChange : true,
	/**
	 * @cfg {Boolean} animate Turn on or off animation. Defaults to true
	 */
	animate : true,

	/**
	 * True while the thumb is in a drag operation
	 * 
	 * @type Boolean
	 */
	dragging : false,

	/**
	 * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
	 */
	constrainThumbs : true,

	/**
	 * @private
	 * @property topThumbZIndex
	 * @type Number The number used internally to set the z index of the top thumb (see promoteThumb for details)
	 */
	topThumbZIndex : 10000,

	// private override
	initComponent : function() {
		if (!Ext.isDefined(this.value)) {
			this.value = this.minValue;
		}

		/**
		 * @property thumbs
		 * @type Array Array containing references to each thumb
		 */
		this.thumbs = [];

		Ext.slider.MultiSlider.superclass.initComponent.call(this);

		this.keyIncrement = Math.max(this.increment, this.keyIncrement);
		this.addEvents(
				/**
				 * @event beforechange Fires before the slider value is changed. By returning false from an event handler, you can cancel the event and prevent the slider from
				 *        changing.
				 * @param {Ext.Slider}
				 *            slider The slider
				 * @param {Number}
				 *            newValue The new value which the slider is being changed to.
				 * @param {Number}
				 *            oldValue The old value which the slider was previously.
				 */
				'beforechange',

				/**
				 * @event change Fires when the slider value is changed.
				 * @param {Ext.Slider}
				 *            slider The slider
				 * @param {Number}
				 *            newValue The new value which the slider has been changed to.
				 * @param {Ext.slider.Thumb}
				 *            thumb The thumb that was changed
				 */
				'change',

				/**
				 * @event changecomplete Fires when the slider value is changed by the user and any drag operations have completed.
				 * @param {Ext.Slider}
				 *            slider The slider
				 * @param {Number}
				 *            newValue The new value which the slider has been changed to.
				 * @param {Ext.slider.Thumb}
				 *            thumb The thumb that was changed
				 */
				'changecomplete',

				/**
				 * @event dragstart Fires after a drag operation has started.
				 * @param {Ext.Slider}
				 *            slider The slider
				 * @param {Ext.EventObject}
				 *            e The event fired from Ext.dd.DragTracker
				 */
				'dragstart',

				/**
				 * @event drag Fires continuously during the drag operation while the mouse is moving.
				 * @param {Ext.Slider}
				 *            slider The slider
				 * @param {Ext.EventObject}
				 *            e The event fired from Ext.dd.DragTracker
				 */
				'drag',

				/**
				 * @event dragend Fires after the drag operation has completed.
				 * @param {Ext.Slider}
				 *            slider The slider
				 * @param {Ext.EventObject}
				 *            e The event fired from Ext.dd.DragTracker
				 */
				'dragend');

		/**
		 * @property values
		 * @type Array Array of values to initalize the thumbs with
		 */
		if (this.values == undefined || Ext.isEmpty(this.values))
			this.values = [0];

		var values = this.values;

		for (var i = 0; i < values.length; i++) {
			this.addThumb(values[i]);
		}

		if (this.vertical) {
			Ext.apply(this, Ext.slider.Vertical);
		}
	},

	/**
	 * Creates a new thumb and adds it to the slider
	 * 
	 * @param {Number}
	 *            value The initial value to set on the thumb. Defaults to 0
	 */
	addThumb : function(value) {
		var thumb = new Ext.slider.Thumb({
					value : value,
					slider : this,
					index : this.thumbs.length,
					constrain : this.constrainThumbs
				});
		this.thumbs.push(thumb);

		// render the thumb now if needed
		if (this.rendered)
			thumb.render();
	},

	/**
	 * @private Moves the given thumb above all other by increasing its z-index. This is called when as drag any thumb, so that the thumb that was just dragged is always at the
	 *          highest z-index. This is required when the thumbs are stacked on top of each other at one of the ends of the slider's range, which can result in the user not being
	 *          able to move any of them.
	 * @param {Ext.slider.Thumb}
	 *            topThumb The thumb to move to the top
	 */
	promoteThumb : function(topThumb) {
		var thumbs = this.thumbs, zIndex, thumb;

		for (var i = 0, j = thumbs.length; i < j; i++) {
			thumb = thumbs[i];

			if (thumb == topThumb) {
				zIndex = this.topThumbZIndex;
			} else {
				zIndex = '';
			}

			thumb.el.setStyle('zIndex', zIndex);
		}
	},

	// private override
	onRender : function() {
		this.autoEl = {
			cls : 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
			cn : {
				cls : 'x-slider-end',
				cn : {
					cls : 'x-slider-inner',
					cn : [{
								tag : 'a',
								cls : 'x-slider-focus',
								href : "#",
								tabIndex : '-1',
								hidefocus : 'on'
							}]
				}
			}
		};

		Ext.slider.MultiSlider.superclass.onRender.apply(this, arguments);

		this.endEl = this.el.first();
		this.innerEl = this.endEl.first();
		this.focusEl = this.innerEl.child('.x-slider-focus');

		// render each thumb
		for (var i = 0; i < this.thumbs.length; i++) {
			this.thumbs[i].render();
		}

		// calculate the size of half a thumb
		var thumb = this.innerEl.child('.x-slider-thumb');
		this.halfThumb = (this.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;

		this.initEvents();
	},

	/**
	 * @private Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element. Creates a new DragTracker which is used to control what happens
	 *          when the user drags the thumb around.
	 */
	initEvents : function() {
		this.mon(this.el, {
					scope : this,
					mousedown : this.onMouseDown,
					keydown : this.onKeyDown
				});

		this.focusEl.swallowEvent("click", true);
	},

	/**
	 * @private Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb', this calculates the new value of the slider and
	 *          tells the implementation (Horizontal or Vertical) to move the thumb
	 * @param {Ext.EventObject}
	 *            e The click event
	 */
	onMouseDown : function(e) {
		if (this.disabled) {
			return;
		}

		// see if the click was on any of the thumbs
		var thumbClicked = false;
		for (var i = 0; i < this.thumbs.length; i++) {
			thumbClicked = thumbClicked || e.target == this.thumbs[i].el.dom;
		}

		if (this.clickToChange && !thumbClicked) {
			var local = this.innerEl.translatePoints(e.getXY());
			this.onClickChange(local);
		}
		this.focus();
	},

	/**
	 * @private Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Vertical. Only changes the value if the click was within
	 *          this.clickRange.
	 * @param {Object}
	 *            local Object containing top and left values for the click event.
	 */
	onClickChange : function(local) {
		if (local.top > this.clickRange[0] && local.top < this.clickRange[1]) {
			// find the nearest thumb to the click event
			var thumb = this.getNearest(local, 'left'), index = thumb.index;

			this.setValue(index, Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
		}
	},

	/**
	 * @private Returns the nearest thumb to a click event, along with its distance
	 * @param {Object}
	 *            local Object containing top and left values from a click event
	 * @param {String}
	 *            prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
	 * @return {Object} The closest thumb object and its distance from the click event
	 */
	getNearest : function(local, prop) {
		var localValue = prop == 'top' ? this.innerEl.getHeight() - local[prop] : local[prop], clickValue = this.reverseValue(localValue), nearestDistance = (this.maxValue - this.minValue)
				+ 5, // add a small fudge for the end of the slider
		index = 0, nearest = null;

		for (var i = 0; i < this.thumbs.length; i++) {
			var thumb = this.thumbs[i], value = thumb.value, dist = Math.abs(value - clickValue);

			if (Math.abs(dist <= nearestDistance)) {
				nearest = thumb;
				index = i;
				nearestDistance = dist;
			}
		}
		return nearest;
	},

	/**
	 * @private Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right by this.keyIncrement. If DOWN or LEFT it is
	 *          moved left. Pressing CTRL moves the slider to the end in either direction
	 * @param {Ext.EventObject}
	 *            e The Event object
	 */
	onKeyDown : function(e) {
		/*
		 * The behaviour for keyboard handling with multiple thumbs is currently undefined. There's no real sane default for it, so leave it like this until we come up with a
		 * better way of doing it.
		 */
		if (this.disabled || this.thumbs.length !== 1) {
			e.preventDefault();
			return;
		}
		var k = e.getKey(), val;
		switch (k) {
			case e.UP :
			case e.RIGHT :
				e.stopEvent();
				val = e.ctrlKey ? this.maxValue : this.getValue(0) + this.keyIncrement;
				this.setValue(0, val, undefined, true);
				break;
			case e.DOWN :
			case e.LEFT :
				e.stopEvent();
				val = e.ctrlKey ? this.minValue : this.getValue(0) - this.keyIncrement;
				this.setValue(0, val, undefined, true);
				break;
			default :
				e.preventDefault();
		}
	},

	/**
	 * @private If using snapping, this takes a desired new value and returns the closest snapped value to it
	 * @param {Number}
	 *            value The unsnapped value
	 * @return {Number} The value of the nearest snap target
	 */
	doSnap : function(value) {
		if (!(this.increment && value)) {
			return value;
		}
		var newValue = value, inc = this.increment, m = value % inc;
		if (m != 0) {
			newValue -= m;
			if (m * 2 >= inc) {
				newValue += inc;
			} else if (m * 2 < -inc) {
				newValue -= inc;
			}
		}
		return newValue.constrain(this.minValue, this.maxValue);
	},

	// private
	afterRender : function() {
		Ext.slider.MultiSlider.superclass.afterRender.apply(this, arguments);

		for (var i = 0; i < this.thumbs.length; i++) {
			var thumb = this.thumbs[i];

			if (thumb.value !== undefined) {
				var v = this.normalizeValue(thumb.value);

				if (v !== thumb.value) {
					// delete this.value;
					this.setValue(i, v, false);
				} else {
					this.moveThumb(i, this.translateValue(v), false);
				}
			}
		};
	},

	/**
	 * @private Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100, the ratio is 2
	 * @return {Number} The ratio of pixels to mapped values
	 */
	getRatio : function() {
		var w = this.innerEl.getWidth(), v = this.maxValue - this.minValue;
		return v == 0 ? w : (w / v);
	},

	/**
	 * @private Returns a snapped, constrained value when given a desired value
	 * @param {Number}
	 *            value Raw number value
	 * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
	 */
	normalizeValue : function(v) {
		v = this.doSnap(v);
		v = Ext.util.Format.round(v, this.decimalPrecision);
		v = v.constrain(this.minValue, this.maxValue);
		return v;
	},

	/**
	 * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current value will be changed.
	 * 
	 * @param {Number}
	 *            val The new minimum value
	 */
	setMinValue : function(val) {
		this.minValue = val;
		var i = 0, thumbs = this.thumbs, len = thumbs.length, t;

		for (; i < len; ++i) {
			t = thumbs[i];
			t.value = t.value < val ? val : t.value;
		}
		this.syncThumb();
	},

	/**
	 * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current value will be changed.
	 * 
	 * @param {Number}
	 *            val The new maximum value
	 */
	setMaxValue : function(val) {
		this.maxValue = val;
		var i = 0, thumbs = this.thumbs, len = thumbs.length, t;

		for (; i < len; ++i) {
			t = thumbs[i];
			t.value = t.value > val ? val : t.value;
		}
		this.syncThumb();
	},

	/**
	 * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and maxValue.
	 * 
	 * @param {Number}
	 *            index Index of the thumb to move
	 * @param {Number}
	 *            value The value to set the slider to. (This will be constrained within minValue and maxValue)
	 * @param {Boolean}
	 *            animate Turn on or off animation, defaults to true
	 */
	setValue : function(index, v, animate, changeComplete) {
		var thumb = this.thumbs[index], el = thumb.el;

		v = this.normalizeValue(v);

		if (v !== thumb.value && this.fireEvent('beforechange', this, v, thumb.value, thumb) !== false) {
			thumb.value = v;
			if (this.rendered) {
				this.moveThumb(index, this.translateValue(v), animate !== false);
				this.fireEvent('change', this, v, thumb);
				if (changeComplete) {
					this.fireEvent('changecomplete', this, v, thumb);
				}
			}
		}
	},

	/**
	 * @private
	 */
	translateValue : function(v) {
		var ratio = this.getRatio();
		return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
	},

	/**
	 * @private Given a pixel location along the slider, returns the mapped slider value for that pixel. E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500,
	 *          reverseValue(50) returns 200
	 * @param {Number}
	 *            pos The position along the slider to return a mapped value for
	 * @return {Number} The mapped value for the given position
	 */
	reverseValue : function(pos) {
		var ratio = this.getRatio();
		return (pos + (this.minValue * ratio)) / ratio;
	},

	/**
	 * @private
	 * @param {Number}
	 *            index Index of the thumb to move
	 */
	moveThumb : function(index, v, animate) {
		var thumb = this.thumbs[index].el;

		if (!animate || this.animate === false) {
			thumb.setLeft(v);
		} else {
			thumb.shift({
						left : v,
						stopFx : true,
						duration : .35
					});
		}
	},

	// private
	focus : function() {
		this.focusEl.focus(10);
	},

	// private
	onResize : function(w, h) {
		var thumbs = this.thumbs, len = thumbs.length, i = 0;

		/*
		 * If we happen to be animating during a resize, the position of the thumb will likely be off when the animation stops. As such, just stop any animations before syncing the
		 * thumbs.
		 */
		for (; i < len; ++i) {
			thumbs[i].el.stopFx();
		}
		this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
		this.syncThumb();
		Ext.slider.MultiSlider.superclass.onResize.apply(this, arguments);
	},

	// private
	onDisable : function() {
		Ext.slider.MultiSlider.superclass.onDisable.call(this);

		for (var i = 0; i < this.thumbs.length; i++) {
			var thumb = this.thumbs[i], el = thumb.el;

			thumb.disable();

			if (Ext.isIE) {
				// IE breaks when using overflow visible and opacity other
				// than 1.
				// Create a place holder for the thumb and display it.
				var xy = el.getXY();
				el.hide();

				this.innerEl.addClass(this.disabledClass).dom.disabled = true;

				if (!this.thumbHolder) {
					this.thumbHolder = this.endEl.createChild({
								cls : 'x-slider-thumb ' + this.disabledClass
							});
				}

				this.thumbHolder.show().setXY(xy);
			}
		}
	},

	// private
	onEnable : function() {
		Ext.slider.MultiSlider.superclass.onEnable.call(this);

		for (var i = 0; i < this.thumbs.length; i++) {
			var thumb = this.thumbs[i], el = thumb.el;

			thumb.enable();

			if (Ext.isIE) {
				this.innerEl.removeClass(this.disabledClass).dom.disabled = false;

				if (this.thumbHolder)
					this.thumbHolder.hide();

				el.show();
				this.syncThumb();
			}
		}
	},

	/**
	 * Synchronizes the thumb position to the proper proportion of the total component width based on the current slider {@link #value}. This will be called automatically when the
	 * Slider is resized by a layout, but if it is rendered auto width, this method can be called from another resize handler to sync the Slider if necessary.
	 */
	syncThumb : function() {
		if (this.rendered) {
			for (var i = 0; i < this.thumbs.length; i++) {
				this.moveThumb(i, this.translateValue(this.thumbs[i].value));
			}
		}
	},

	/**
	 * Returns the current value of the slider
	 * 
	 * @param {Number}
	 *            index The index of the thumb to return a value for
	 * @return {Number} The current value of the slider
	 */
	getValue : function(index) {
		return this.thumbs[index].value;
	},

	/**
	 * Returns an array of values - one for the location of each thumb
	 * 
	 * @return {Array} The set of thumb values
	 */
	getValues : function() {
		var values = [];

		for (var i = 0; i < this.thumbs.length; i++) {
			values.push(this.thumbs[i].value);
		}

		return values;
	},

	// private
	beforeDestroy : function() {
		Ext.destroyMembers(this, 'endEl', 'innerEl', 'thumb', 'halfThumb', 'focusEl', 'tracker', 'thumbHolder');
		Ext.slider.MultiSlider.superclass.beforeDestroy.call(this);
	}
});

Ext.reg('multislider', Ext.slider.MultiSlider);

/**
 * @class Ext.slider.SingleSlider
 * @extends Ext.slider.MultiSlider Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking and animation. Can be added
 *          as an item to any container. Example usage:
 * 
 * <pre><code>
 * new Ext.slider.SingleSlider({
 * 			renderTo : Ext.getBody(),
 * 			width : 200,
 * 			value : 50,
 * 			increment : 10,
 * 			minValue : 0,
 * 			maxValue : 100
 * 		});
 * </code></pre>
 * 
 * The class Ext.slider.SingleSlider is aliased to Ext.Slider for backwards compatibility.
 */
Ext.slider.SingleSlider = Ext.extend(Ext.slider.MultiSlider, {
			constructor : function(config) {
				config = config || {};

				Ext.applyIf(config, {
							values : [config.value || 0]
						});

				Ext.slider.SingleSlider.superclass.constructor.call(this, config);
			},

			/**
			 * Returns the current value of the slider
			 * 
			 * @return {Number} The current value of the slider
			 */
			getValue : function() {
				// just returns the value of the first thumb, which should
				// be the only one in a single slider
				return Ext.slider.SingleSlider.superclass.getValue.call(this, 0);
			},

			/**
			 * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and maxValue.
			 * 
			 * @param {Number}
			 *            value The value to set the slider to. (This will be constrained within minValue and maxValue)
			 * @param {Boolean}
			 *            animate Turn on or off animation, defaults to true
			 */
			setValue : function(value, animate) {
				var args = Ext.toArray(arguments), len = args.length;

				// this is to maintain backwards compatiblity for sliders
				// with only one thunb. Usually you must pass the thumb
				// index to setValue, but if we only have one thumb we
				// inject the index here first if given the multi-slider
				// signature without the required index. The index will
				// always be 0 for a single slider
				if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
					args.unshift(0);
				}

				return Ext.slider.SingleSlider.superclass.setValue.apply(this, args);
			},

			/**
			 * Synchronizes the thumb position to the proper proportion of the total component width based on the current slider {@link #value}. This will be called automatically
			 * when the Slider is resized by a layout, but if it is rendered auto width, this method can be called from another resize handler to sync the Slider if necessary.
			 */
			syncThumb : function() {
				return Ext.slider.SingleSlider.superclass.syncThumb.apply(this, [0].concat(arguments));
			},

			// private
			getNearest : function() {
				// Since there's only 1 thumb, it's always the nearest
				return this.thumbs[0];
			}
		});

// backwards compatibility
Ext.Slider = Ext.slider.SingleSlider;

Ext.reg('slider', Ext.slider.SingleSlider);

// private class to support vertical sliders
Ext.slider.Vertical = {
	onResize : function(w, h) {
		this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
		this.syncThumb();
	},

	getRatio : function() {
		var h = this.innerEl.getHeight(), v = this.maxValue - this.minValue;
		return h / v;
	},

	moveThumb : function(index, v, animate) {
		var thumb = this.thumbs[index], el = thumb.el;

		if (!animate || this.animate === false) {
			el.setBottom(v);
		} else {
			el.shift({
						bottom : v,
						stopFx : true,
						duration : .35
					});
		}
	},

	onClickChange : function(local) {
		if (local.left > this.clickRange[0] && local.left < this.clickRange[1]) {
			var thumb = this.getNearest(local, 'top'), index = thumb.index, value = this.minValue + this.reverseValue(this.innerEl.getHeight() - local.top);

			this.setValue(index, Ext.util.Format.round(value, this.decimalPrecision), undefined, true);
		}
	}
};

// private class to support vertical dragging of thumbs within a slider
Ext.slider.Thumb.Vertical = {
	getNewValue : function() {
		var slider = this.slider, innerEl = slider.innerEl, pos = innerEl.translatePoints(this.tracker.getXY()), bottom = innerEl.getHeight() - pos.top;

		return slider.minValue + Ext.util.Format.round(bottom / slider.getRatio(), slider.decimalPrecision);
	}
};

Ext.form.SliderField = Ext.extend(Ext.form.Field, {

			/**
			 * @cfg {Boolean} useTips True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
			 */
			useTips : true,

			showValue : true,

			/**
			 * @cfg {Function} tipText A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will use the default on the plugin.
			 */
			tipText : null,

			// private override
			actionMode : 'wrap',

			/**
			 * Initialize the component.
			 * 
			 * @private
			 */
			initComponent : function() {
				var sliderConfig = this.sliderConfig || {};
				sliderConfig.id = this.id + '-slider';
				var cfg = Ext.copyTo(sliderConfig, this.initialConfig, ['vertical', 'minValue', 'maxValue', 'decimalPrecision', 'keyIncrement', 'increment', 'clickToChange',
								'animate']);

				// only can use it if it exists.
				if (this.useTips) {
					var plug = this.tipText ? {
						getText : this.tipText
					} : {};
					cfg.plugins = [new Ext.slider.Tip(plug)];
				}
				this.slider = new Ext.Slider(cfg);
				Ext.form.SliderField.superclass.initComponent.call(this);
			},

			/**
			 * Set up the hidden field
			 * 
			 * @param {Object}
			 *            ct The container to render to.
			 * @param {Object}
			 *            position The position in the container to render to.
			 * @private
			 */
			onRender : function(ct, position) {
				this.autoCreate = {
					id : this.id,
					name : this.name,
					type : 'hidden',
					tag : 'input'
				};
				Ext.form.SliderField.superclass.onRender.call(this, ct, position);
				this.wrap = this.el.wrap({
							cls : 'x-form-field-wrap'
						});
				this.resizeEl = this.positionEl = this.wrap;
				this.slider.render(this.wrap);
				if (this.showValue) {
					this.valueTipEl = this.wrap.createChild({
								tag : 'div',
								style : {
									position : 'relative',
									left : (this.width + 5) + 'px',
									top : "-" + (this.height ? (this.height - 2) : 20) + 'px'
								},
								html : this.tipText ? this.tipText({
											value : this.getValue()
										}) : {
									value : this.getValue()
								}
							});
				}
			},

			/**
			 * Ensure that the slider size is set automatically when the field resizes.
			 * 
			 * @param {Object}
			 *            w The width
			 * @param {Object}
			 *            h The height
			 * @param {Object}
			 *            aw The adjusted width
			 * @param {Object}
			 *            ah The adjusted height
			 * @private
			 */
			onResize : function(w, h, aw, ah) {
				Ext.form.SliderField.superclass.onResize.call(this, w, h, aw, ah);
				this.slider.setSize(w, h);
			},

			/**
			 * Initialize any events for this class.
			 * 
			 * @private
			 */
			initEvents : function() {
				Ext.form.SliderField.superclass.initEvents.call(this);
				this.slider.on('change', this.onChange, this);
			},

			/**
			 * Utility method to set the value of the field when the slider changes.
			 * 
			 * @param {Object}
			 *            slider The slider object.
			 * @param {Object}
			 *            v The new value.
			 * @private
			 */
			onChange : function(slider, v) {
				this.setValue(v, undefined, true);
			},

			/**
			 * Enable the slider when the field is enabled.
			 * 
			 * @private
			 */
			onEnable : function() {
				Ext.form.SliderField.superclass.onEnable.call(this);
				this.slider.enable();
			},

			/**
			 * Disable the slider when the field is disabled.
			 * 
			 * @private
			 */
			onDisable : function() {
				Ext.form.SliderField.superclass.onDisable.call(this);
				this.slider.disable();
			},

			/**
			 * Ensure the slider is destroyed when the field is destroyed.
			 * 
			 * @private
			 */
			beforeDestroy : function() {
				Ext.destroy(this.slider);
				Ext.form.SliderField.superclass.beforeDestroy.call(this);
			},

			/**
			 * If a side icon is shown, do alignment to the slider
			 * 
			 * @private
			 */
			alignErrorIcon : function() {
				this.errorIcon.alignTo(this.slider.el, 'tl-tr', [2, 0]);
			},

			/**
			 * Sets the minimum field value.
			 * 
			 * @param {Number}
			 *            v The new minimum value.
			 * @return {Ext.form.SliderField} this
			 */
			setMinValue : function(v) {
				this.slider.setMinValue(v);
				return this;
			},

			/**
			 * Sets the maximum field value.
			 * 
			 * @param {Number}
			 *            v The new maximum value.
			 * @return {Ext.form.SliderField} this
			 */
			setMaxValue : function(v) {
				this.slider.setMaxValue(v);
				return this;
			},

			/**
			 * Sets the value for this field.
			 * 
			 * @param {Number}
			 *            v The new value.
			 * @param {Boolean}
			 *            animate (optional) Whether to animate the transition. If not specified, it will default to the animate config.
			 * @return {Ext.form.SliderField} this
			 */
			setValue : function(v, animate, /* private */silent) {
				// silent is used if the setValue method is invoked by the
				// slider
				// which means we don't need to set the value on the slider.
				if (!silent) {
					this.slider.setValue(v, animate);
				}

				this.setValueTip(this.getValue(), true);

				return Ext.form.SliderField.superclass.setValue.call(this, this.slider.getValue());
			},

			setValueTip : function(value, useTipText) {
				if (this.valueTipEl) {
					this.valueTipEl.update((this.tipText && useTipText) ? this.tipText({
								value : value
							}) : value);
				}
			},

			/**
			 * Gets the current value for this field.
			 * 
			 * @return {Number} The current value.
			 */
			getValue : function() {
				return this.slider.getValue();
			}
		});

Ext.reg('sliderfield', Ext.form.SliderField);

Ext.Button = Ext.extend(Ext.BoxComponent, {

	hidden : false,

	disabled : false,

	pressed : false,

	enableToggle : false,

	menuAlign : 'tl-bl?',

	type : 'button',

	menuClassTarget : 'tr:nth(2)',

	clickEvent : 'click',

	handleMouseEvents : true,

	tooltipType : 'qtip',

	buttonSelector : 'button:first-child',

	scale : 'small',

	iconAlign : 'left',

	arrowAlign : 'right',

	initComponent : function() {
		Ext.Button.superclass.initComponent.call(this);

		this.addEvents(

				'click',

				'toggle',

				'mouseover',

				'mouseout',

				'menushow',

				'menuhide',

				'menutriggerover',

				'menutriggerout');
		if (this.menu) {
			this.menu = Ext.menu.MenuMgr.get(this.menu);
		}
		if (Ext.isString(this.toggleGroup)) {
			this.enableToggle = true;
		}
	},

	getTemplateArgs : function() {
		return [this.type, 'x-btn-' + this.scale + ' x-btn-icon-' + this.scale + '-' + this.iconAlign, this.getMenuClass(), this.cls, this.id];
	},

	setButtonClass : function() {
		if (this.useSetClass) {
			if (!Ext.isEmpty(this.oldCls)) {
				this.el.removeClass([this.oldCls, 'x-btn-pressed']);
			}
			this.oldCls = (this.iconCls || this.icon) ? (this.text ? 'x-btn-text-icon' : 'x-btn-icon') : 'x-btn-noicon';
			this.el.addClass([this.oldCls, this.pressed ? 'x-btn-pressed' : null]);
		}
	},

	getMenuClass : function() {
		return this.menu ? (this.arrowAlign != 'bottom' ? 'x-btn-arrow' : 'x-btn-arrow-bottom') : '';
	},

	onRender : function(ct, position) {
		if (!this.template) {
			if (!Ext.Button.buttonTemplate) {

				Ext.Button.buttonTemplate = new Ext.Template(
						'<table id="{4}" cellspacing="0" class="x-btn {3}"><tbody class="{1}">',
						'<tr><td class="x-btn-tl"><i>&#160;</i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i>&#160;</i></td></tr>',
						'<tr><td class="x-btn-ml"><i>&#160;</i></td><td class="x-btn-mc"><em class="{2}" unselectable="on"><button type="{0}"></button></em></td><td class="x-btn-mr"><i>&#160;</i></td></tr>',
						'<tr><td class="x-btn-bl"><i>&#160;</i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i>&#160;</i></td></tr>', '</tbody></table>');
				Ext.Button.buttonTemplate.compile();
			}
			this.template = Ext.Button.buttonTemplate;
		}

		var btn, targs = this.getTemplateArgs();

		if (position) {
			btn = this.template.insertBefore(position, targs, true);
		} else {
			btn = this.template.append(ct, targs, true);
		}

		this.btnEl = btn.child(this.buttonSelector);
		this.mon(this.btnEl, {
					scope : this,
					focus : this.onFocus,
					blur : this.onBlur
				});

		this.initButtonEl(btn, this.btnEl);

		Ext.ButtonToggleMgr.register(this);
	},

	initButtonEl : function(btn, btnEl) {
		this.el = btn;
		this.setIcon(this.icon);
		this.setText(this.text);
		this.setIconClass(this.iconCls);
		if (Ext.isDefined(this.tabIndex)) {
			btnEl.dom.tabIndex = this.tabIndex;
		}
		if (this.tooltip) {
			this.setTooltip(this.tooltip, true);
		}

		if (this.handleMouseEvents) {
			this.mon(btn, {
						scope : this,
						mouseover : this.onMouseOver,
						mousedown : this.onMouseDown
					});

		}

		if (this.menu) {
			this.mon(this.menu, {
						scope : this,
						show : this.onMenuShow,
						hide : this.onMenuHide
					});
		}

		if (this.repeat) {
			var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {});
			this.mon(repeater, 'click', this.onClick, this);
		}
		this.mon(btn, this.clickEvent, this.onClick, this);
	},

	afterRender : function() {
		Ext.Button.superclass.afterRender.call(this);
		this.useSetClass = true;
		this.setButtonClass();
		this.doc = Ext.getDoc();
		this.doAutoWidth();
	},

	setIconClass : function(cls) {
		this.iconCls = cls;
		if (this.el) {
			this.btnEl.dom.className = '';
			this.btnEl.addClass(['x-btn-text', cls || '']);
			this.setButtonClass();
		}
		return this;
	},

	setTooltip : function(tooltip, initial) {
		if (this.rendered) {
			if (!initial) {
				this.clearTip();
			}
			if (Ext.isObject(tooltip)) {
				Ext.QuickTips.register(Ext.apply({
							target : this.btnEl.id
						}, tooltip));
				this.tooltip = tooltip;
			} else {
				this.btnEl.dom[this.tooltipType] = tooltip;
			}
		} else {
			this.tooltip = tooltip;
		}
		return this;
	},

	clearTip : function() {
		if (Ext.isObject(this.tooltip)) {
			Ext.QuickTips.unregister(this.btnEl);
		}
	},

	beforeDestroy : function() {
		if (this.rendered) {
			this.clearTip();
		}
		if (this.menu && this.destroyMenu !== false) {
			Ext.destroy(this.menu);
		}
		Ext.destroy(this.repeater);
	},

	onDestroy : function() {
		if (this.rendered) {
			this.doc.un('mouseover', this.monitorMouseOver, this);
			this.doc.un('mouseup', this.onMouseUp, this);
			delete this.doc;
			delete this.btnEl;
			Ext.ButtonToggleMgr.unregister(this);
		}
		Ext.Button.superclass.onDestroy.call(this);
	},

	doAutoWidth : function() {
		if (this.autoWidth !== false && this.el && this.text && this.width === undefined) {
			this.el.setWidth('auto');
			if (Ext.isIE7 && Ext.isStrict) {
				var ib = this.btnEl;
				if (ib && ib.getWidth() > 20) {
					ib.clip();
					ib.setWidth(Ext.util.TextMetrics.measure(ib, this.text).width + ib.getFrameWidth('lr'));
				}
			}
			if (this.minWidth) {
				if (this.el.getWidth() < this.minWidth) {
					this.el.setWidth(this.minWidth);
				}
			}
		}
	},

	setHandler : function(handler, scope) {
		this.handler = handler;
		this.scope = scope;
		return this;
	},

	setText : function(text) {
		this.text = text;
		if (this.el) {
			this.btnEl.update(text || '&#160;');
			this.setButtonClass();
		}
		this.doAutoWidth();
		return this;
	},

	setIcon : function(icon) {
		this.icon = icon;
		if (this.el) {
			this.btnEl.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
			this.setButtonClass();
		}
		return this;
	},

	getText : function() {
		return this.text;
	},

	toggle : function(state, suppressEvent) {
		state = state === undefined ? !this.pressed : !!state;
		if (state != this.pressed) {
			if (this.rendered) {
				this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed');
			}
			this.pressed = state;
			if (!suppressEvent) {
				this.fireEvent('toggle', this, state);
				if (this.toggleHandler) {
					this.toggleHandler.call(this.scope || this, this, state);
				}
			}
		}
		return this;
	},

	onDisable : function() {
		this.onDisableChange(true);
	},

	onEnable : function() {
		this.onDisableChange(false);
	},

	onDisableChange : function(disabled) {
		if (this.el) {
			if (!Ext.isIE6 || !this.text) {
				this.el[disabled ? 'addClass' : 'removeClass'](this.disabledClass);
			}
			this.el.dom.disabled = disabled;
		}
		this.disabled = disabled;
	},

	showMenu : function() {
		if (this.rendered && this.menu) {
			if (this.tooltip) {
				Ext.QuickTips.getQuickTip().cancelShow(this.btnEl);
			}
			if (this.menu.isVisible()) {
				this.menu.hide();
			}
			this.menu.ownerCt = this;
			this.menu.show(this.el, this.menuAlign);
		}
		return this;
	},

	hideMenu : function() {
		if (this.hasVisibleMenu()) {
			this.menu.hide();
		}
		return this;
	},

	hasVisibleMenu : function() {
		return this.menu && this.menu.ownerCt == this && this.menu.isVisible();
	},

	onClick : function(e) {
		if (e) {
			e.preventDefault();
		}
		if (e.button !== 0) {
			return;
		}
		if (!this.disabled) {
			if (this.enableToggle && (this.allowDepress !== false || !this.pressed)) {
				this.toggle();
			}
			if (this.menu && !this.hasVisibleMenu() && !this.ignoreNextClick) {
				this.showMenu();
			}
			this.fireEvent('click', this, e);
			if (this.handler) {

				this.handler.call(this.scope || this, this, e);
			}
		}
	},

	isMenuTriggerOver : function(e, internal) {
		return this.menu && !internal;
	},

	isMenuTriggerOut : function(e, internal) {
		return this.menu && !internal;
	},

	onMouseOver : function(e) {
		if (!this.disabled) {
			var internal = e.within(this.el, true);
			if (!internal) {
				this.el.addClass('x-btn-over');
				if (!this.monitoringMouseOver) {
					this.doc.on('mouseover', this.monitorMouseOver, this);
					this.monitoringMouseOver = true;
				}
				this.fireEvent('mouseover', this, e);
			}
			if (this.isMenuTriggerOver(e, internal)) {
				this.fireEvent('menutriggerover', this, this.menu, e);
			}
		}
	},

	monitorMouseOver : function(e) {
		if (e.target != this.el.dom && !e.within(this.el)) {
			if (this.monitoringMouseOver) {
				this.doc.un('mouseover', this.monitorMouseOver, this);
				this.monitoringMouseOver = false;
			}
			this.onMouseOut(e);
		}
	},

	onMouseOut : function(e) {
		var internal = e.within(this.el) && e.target != this.el.dom;
		this.el.removeClass('x-btn-over');
		this.fireEvent('mouseout', this, e);
		if (this.isMenuTriggerOut(e, internal)) {
			this.fireEvent('menutriggerout', this, this.menu, e);
		}
	},

	focus : function() {
		this.btnEl.focus();
	},

	blur : function() {
		this.btnEl.blur();
	},

	onFocus : function(e) {
		if (!this.disabled) {
			this.el.addClass('x-btn-focus');
		}
	},

	onBlur : function(e) {
		this.el.removeClass('x-btn-focus');
	},

	getClickEl : function(e, isUp) {
		return this.el;
	},

	onMouseDown : function(e) {
		if (!this.disabled && e.button === 0) {
			this.getClickEl(e).addClass('x-btn-click');
			this.doc.on('mouseup', this.onMouseUp, this);
		}
	},

	onMouseUp : function(e) {
		if (e.button === 0) {
			this.getClickEl(e, true).removeClass('x-btn-click');
			this.doc.un('mouseup', this.onMouseUp, this);
		}
	},

	onMenuShow : function(e) {
		if (this.menu.ownerCt == this) {
			this.menu.ownerCt = this;
			this.ignoreNextClick = 0;
			this.el.addClass('x-btn-menu-active');
			this.fireEvent('menushow', this, this.menu);
		}
	},

	onMenuHide : function(e) {
		if (this.menu.ownerCt == this) {
			this.el.removeClass('x-btn-menu-active');
			this.ignoreNextClick = this.restoreClick.defer(250, this);
			this.fireEvent('menuhide', this, this.menu);
			delete this.menu.ownerCt;
		}
	},

	restoreClick : function() {
		this.ignoreNextClick = 0;
	}

});
Ext.reg('button', Ext.Button);

Ext.ButtonToggleMgr = function() {
	var groups = {};

	function toggleGroup(btn, state) {
		if (state) {
			var g = groups[btn.toggleGroup];
			for (var i = 0, l = g.length; i < l; i++) {
				if (g[i] != btn) {
					g[i].toggle(false);
				}
			}
		}
	}

	return {
		register : function(btn) {
			if (!btn.toggleGroup) {
				return;
			}
			var g = groups[btn.toggleGroup];
			if (!g) {
				g = groups[btn.toggleGroup] = [];
			}
			g.push(btn);
			btn.on('toggle', toggleGroup);
		},

		unregister : function(btn) {
			if (!btn.toggleGroup) {
				return;
			}
			var g = groups[btn.toggleGroup];
			if (g) {
				g.remove(btn);
				btn.un('toggle', toggleGroup);
			}
		},

		getPressed : function(group) {
			var g = groups[group];
			if (g) {
				for (var i = 0, len = g.length; i < len; i++) {
					if (g[i].pressed === true) {
						return g[i];
					}
				}
			}
			return null;
		}
	};
}();

Ext.SplitButton = Ext.extend(Ext.Button, {

			arrowSelector : 'em',
			split : true,

			initComponent : function() {
				Ext.SplitButton.superclass.initComponent.call(this);

				this.addEvents("arrowclick");
			},

			onRender : function() {
				Ext.SplitButton.superclass.onRender.apply(this, arguments);
				if (this.arrowTooltip) {
					this.el.child(this.arrowSelector).dom[this.tooltipType] = this.arrowTooltip;
				}
			},

			setArrowHandler : function(handler, scope) {
				this.arrowHandler = handler;
				this.scope = scope;
			},

			getMenuClass : function() {
				return 'x-btn-split' + (this.arrowAlign == 'bottom' ? '-bottom' : '');
			},

			isClickOnArrow : function(e) {
				if (this.arrowAlign != 'bottom') {
					var visBtn = this.el.child('em.x-btn-split');
					var right = visBtn.getRegion().right - visBtn.getPadding('r');
					return e.getPageX() > right;
				} else {
					return e.getPageY() > this.btnEl.getRegion().bottom;
				}
			},

			onClick : function(e, t) {
				e.preventDefault();
				if (!this.disabled) {
					if (this.isClickOnArrow(e)) {
						if (this.menu && !this.menu.isVisible() && !this.ignoreNextClick) {
							this.showMenu();
						}
						this.fireEvent("arrowclick", this, e);
						if (this.arrowHandler) {
							this.arrowHandler.call(this.scope || this, this, e);
						}
					} else {
						if (this.enableToggle) {
							this.toggle();
						}
						this.fireEvent("click", this, e);
						if (this.handler) {
							this.handler.call(this.scope || this, this, e);
						}
					}
				}
			},

			isMenuTriggerOver : function(e) {
				return this.menu && e.target.tagName == this.arrowSelector;
			},

			isMenuTriggerOut : function(e, internal) {
				return this.menu && e.target.tagName != this.arrowSelector;
			}
		});

Ext.reg('splitbutton', Ext.SplitButton);
Ext.CycleButton = Ext.extend(Ext.SplitButton, {

			getItemText : function(item) {
				if (item && this.showText === true) {
					var text = '';
					if (this.prependText) {
						text += this.prependText;
					}
					text += item.text;
					return text;
				}
				return undefined;
			},

			setActiveItem : function(item, suppressEvent) {
				if (!Ext.isObject(item)) {
					item = this.menu.getComponent(item);
				}
				if (item) {
					if (!this.rendered) {
						this.text = this.getItemText(item);
						this.iconCls = item.iconCls;
					} else {
						var t = this.getItemText(item);
						if (t) {
							this.setText(t);
						}
						this.setIconClass(item.iconCls);
					}
					this.activeItem = item;
					if (!item.checked) {
						item.setChecked(true, false);
					}
					if (this.forceIcon) {
						this.setIconClass(this.forceIcon);
					}
					if (!suppressEvent) {
						this.fireEvent('change', this, item);
					}
				}
			},

			getActiveItem : function() {
				return this.activeItem;
			},

			initComponent : function() {
				this.addEvents(

						"change");

				if (this.changeHandler) {
					this.on('change', this.changeHandler, this.scope || this);
					delete this.changeHandler;
				}

				this.itemCount = this.items.length;

				this.menu = {
					cls : 'x-cycle-menu',
					items : []
				};
				var checked = 0;
				Ext.each(this.items, function(item, i) {
							Ext.apply(item, {
										group : item.group || this.id,
										itemIndex : i,
										checkHandler : this.checkHandler,
										scope : this,
										checked : item.checked || false
									});
							this.menu.items.push(item);
							if (item.checked) {
								checked = i;
							}
						}, this);
				Ext.CycleButton.superclass.initComponent.call(this);
				this.on('click', this.toggleSelected, this);
				this.setActiveItem(checked, true);
			},

			checkHandler : function(item, pressed) {
				if (pressed) {
					this.setActiveItem(item);
				}
			},

			toggleSelected : function() {
				var m = this.menu;
				m.render();

				if (!m.hasLayout) {
					m.doLayout();
				}

				var nextIdx, checkItem;
				for (var i = 1; i < this.itemCount; i++) {
					nextIdx = (this.activeItem.itemIndex + i) % this.itemCount;

					checkItem = m.items.itemAt(nextIdx);

					if (!checkItem.disabled) {
						checkItem.setChecked(true);
						break;
					}
				}
			}
		});
Ext.reg('cycle', Ext.CycleButton);

var MOUSEOVER = 'mouseover';
var MOUSEOUT = 'mouseout';
var pub = {
	extAdapter : true,
	onAvailable : function(p_id, p_fn, p_obj, p_override) {
		onAvailStack.push({
					id : p_id,
					fn : p_fn,
					obj : p_obj,
					override : p_override,
					checkReady : false
				});

		retryCount = POLL_RETRYS;
		startInterval();
	},

	addListener : function(el, eventName, fn) {
		el = Ext.getDom(el);
		if (el && fn) {
			if (eventName == UNLOAD) {
				if (unloadListeners[el.id] === undefined) {
					unloadListeners[el.id] = [];
				}
				unloadListeners[el.id].push([eventName, fn]);
				return fn;
			}
			return doAdd(el, eventName, fn, false);
		}
		return false;
	},

	removeListener : function(el, eventName, fn) {
		el = Ext.getDom(el);
		var i, len, li;
		if (el && fn) {
			if (eventName == UNLOAD) {
				if (unloadListeners[id] !== undefined) {
					for (i = 0, len = unloadListeners[id].length; i < len; i++) {
						li = unloadListeners[id][i];
						if (li && li[TYPE] == eventName && li[FN] == fn) {
							unloadListeners[id].splice(i, 1);
						}
					}
				}
				return;
			}
			doRemove(el, eventName, fn, false);
		}
	},

	getTarget : function(ev) {
		ev = ev.browserEvent || ev;
		return this.resolveTextNode(ev.target || ev.srcElement);
	},

	resolveTextNode : Ext.isGecko ? function(node) {
		if (!node) {
			return;
		}

		var s = HTMLElement.prototype.toString.call(node);
		if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
			return;
		}
		return node.nodeType == 3 ? node.parentNode : node;
	} : function(node) {
		return node && node.nodeType == 3 ? node.parentNode : node;
	},

	getRelatedTarget : function(ev) {
		ev = ev.browserEvent || ev;
		return this.resolveTextNode(ev.relatedTarget || (ev.type == MOUSEOUT ? ev.toElement : ev.type == MOUSEOVER ? ev.fromElement : null));
	},

	getPageX : function(ev) {
		return getPageCoord(ev, "X");
	},

	getPageY : function(ev) {
		return getPageCoord(ev, "Y");
	},

	getXY : function(ev) {
		return [this.getPageX(ev), this.getPageY(ev)];
	},

	stopEvent : function(ev) {
		this.stopPropagation(ev);
		this.preventDefault(ev);
	},

	stopPropagation : function(ev) {
		ev = ev.browserEvent || ev;
		if (ev.stopPropagation) {
			ev.stopPropagation();
		} else {
			ev.cancelBubble = true;
		}
	},

	preventDefault : function(ev) {
		ev = ev.browserEvent || ev;
		if (ev.preventDefault) {
			ev.preventDefault();
		} else {
			ev.returnValue = false;
		}
	},

	getEvent : function(e) {
		e = e || win.event;
		if (!e) {
			var c = this.getEvent.caller;
			while (c) {
				e = c.arguments[0];
				if (e && Event == e.constructor) {
					break;
				}
				c = c.caller;
			}
		}
		return e;
	},

	getCharCode : function(ev) {
		ev = ev.browserEvent || ev;
		return ev.charCode || ev.keyCode || 0;
	},

	getListeners : function(el, eventName) {
		Ext.EventManager.getListeners(el, eventName);
	},

	purgeElement : function(el, recurse, eventName) {
		Ext.EventManager.purgeElement(el, recurse, eventName);
	},

	_load : function(e) {
		loadComplete = true;
		var EU = Ext.lib.Event;
		if (Ext.isIE && e !== true) {

			doRemove(win, "load", arguments.callee);
		}
	},

	_unload : function(e) {
		var EU = Ext.lib.Event, i, j, l, v, ul, id, len, index, scope;

		for (id in unloadListeners) {
			ul = unloadListeners[id];
			for (i = 0, len = ul.length; i < len; i++) {
				v = ul[i];
				if (v) {
					try {
						scope = v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) : win;
						v[FN].call(scope, EU.getEvent(e), v[OBJ]);
					} catch (ex) {
					}
				}
			}
		};

		unloadListeners = null;
		Ext.EventManager._unload();

		doRemove(win, UNLOAD, EU._unload);
	}
};

function checkRelatedTarget(e) {
	return !elContains(e.currentTarget, pub.getRelatedTarget(e));
}

function elContains(parent, child) {
	if (parent && parent.firstChild) {
		while (child) {
			if (child === parent) {
				return true;
			}
			child = child.parentNode;
			if (child && (child.nodeType != 1)) {
				child = null;
			}
		}
	}
	return false;
}
Ext.apply(Ext.lib.Event, {
			doAdd : function() {
				if (window.addEventListener) {
					return function(el, eventName, fn, capture) {
						if (eventName == 'mouseenter') {
							fn = fn.createInterceptor(checkRelatedTarget);
							el.addEventListener(MOUSEOVER, fn, (capture));
						} else if (eventName == 'mouseleave') {
							fn = fn.createInterceptor(checkRelatedTarget);
							el.addEventListener(MOUSEOUT, fn, (capture));
						} else {
							el.addEventListener(eventName, fn, (capture));
						}
					};
				} else if (window.attachEvent) {
					return function(el, eventName, fn, capture) {
						el.attachEvent("on" + eventName, fn);
					};
				} else {
					return function() {
					};
				}
			}(),

			doRemove : function() {
				if (window.removeEventListener) {
					return function(el, eventName, fn, capture) {
						if (eventName == 'mouseenter') {
							eventName = MOUSEOVER;
						} else if (eventName == 'mouseleave') {
							eventName = MOUSEOUT;
						}
						el.removeEventListener(eventName, fn, (capture));
					};
				} else if (window.detachEvent) {
					return function(el, eventName, fn) {
						el.detachEvent("on" + eventName, fn);
					};
				} else {
					return function() {
					};
				}
			}()
		});

var freqency2period = function(freqency) {
	if (freqency == "semi-yearly") {
		return 2.0;
	} else if (freqency == "monthly") {
		return 12.0;
	} else if (freqency == "semi-monthly") {
		return 24.0;
	} else if (freqency == "weekly") {
		return 52.0;
	}
}

var periodicRateFormula = function(interestRate, interestAccrualFreqency, paymentFreqency) {
	var c = freqency2period(interestAccrualFreqency);
	var p = freqency2period(paymentFreqency);
	return Math.pow((1 + interestRate / c), (c / p)) - 1;
}

var mortgagePaymentFormula = function(mortgageAmount, periodicRate, amortizationPeriod) {
	var count = amortizationPeriod;
	return mortgageAmount * (periodicRate + periodicRate / (Math.pow(1 + periodicRate, count) - 1));
};

var mortgageAmountFormula = function(mortgagePayment, periodicRate, amortizationPeriod) {
	var count = amortizationPeriod;
	return mortgagePayment / (periodicRate + periodicRate / (Math.pow(1 + periodicRate, count) - 1));
};

var doCalculate = function(isMortgagePayment) {
	var form = Ext.getCmp("interestRateForm").getForm();
	if (form.isValid()) {
		var v = form.getValues();
		v["interestAccrualFreqency"] = "semi-yearly";
		var periodicRate = periodicRateFormula(parseFloat(v["interestRate"]) / 100, v["interestAccrualFreqency"], v["paymentFreqency"]);
		if (isMortgagePayment == "mortgagePayment") {
			var mortgageAmount = mortgageAmountFormula(parseFloat(v["mortgagePayment"]), periodicRate, parseFloat(v["amortizationPeriod"]) * freqency2period(v["paymentFreqency"]));
			var field = form.findField("mortgageAmount");
			field.setValue(mortgageAmount);
			if (mortgageAmount > field.slider.maxValue) {
				field.setValueTip("Data Overflow!", false);
			} else {
				field.setValueTip(mortgageAmount, true);
			}
		} else {
			var mortgagePayment = mortgagePaymentFormula(parseFloat(v["mortgageAmount"]), periodicRate, parseFloat(v["amortizationPeriod"]) * freqency2period(v["paymentFreqency"]));
			var field = form.findField("mortgagePayment");
			field.setValue(mortgagePayment);
			if (mortgagePayment > field.slider.maxValue) {
				field.setValueTip("Data Overflow!", false);
			} else {
				field.setValueTip(mortgagePayment, true);
			}
		}
	}
};

var calculatorData = [['monthly', 'monthly'], ['semi-monthly', 'semi-monthly'], ['weekly', 'weekly']];
var calculatorItems = {
	// title : 'form title',
	xtype : 'form',
	border : false,
	// labelAlign: 'top',
	id : 'interestRateForm',
	width : 540,
	height : 400,
	labelWidth : 180,
	bodyStyle : 'padding:5px 5px 5px 5px',
	items : [	
	         /*
			 new Ext.form.ComboBox({
			 allowBlank : false,
			 store : new Ext.data.SimpleStore({
			 fields : ['value', 'display'],
			 data : calculatorData
			 }),
			 displayField : 'display',
			 typeAhead : true,
			 mode : 'local',
			 forceSelection : true,
			 triggerAction : 'all',
			 emptyText : 'Select',
			 selectOnFocus : true,
			 name : "interestAccrualFreqency",
			 //fieldLabel : "INTEREST ACCRUAL FREQENCY",
			 value : "semi-yearly",
			 hidden : true,
			 listeners : {
			 "select" : function() {
			 doCalculate();
			 }
			 }
			 }),*/
			new Ext.form.ComboBox({
						allowBlank : false,
						store : new Ext.data.SimpleStore({
									fields : ['value', 'display'],
									data : calculatorData
								}),
						displayField : 'display',
						typeAhead : true,
						mode : 'local',
						forceSelection : true,
						triggerAction : 'all',
						emptyText : 'Select',
						selectOnFocus : true,
						name : "paymentFreqency",
						fieldLabel : "Payment Frequency",
						value : "monthly",
						listeners : {
							"select" : function() {
								doCalculate();
							}
						}
					}), {
				fieldLabel : 'Interest Rate',
				width : 150,
				value : 3,
				increment : 0.05,
				minValue : 0.05,
				maxValue : 10,
				xtype : 'sliderfield',
				name : 'interestRate',
				tipText : function(thumb) {
					return String(thumb.value) + '%';
				},
				sliderConfig : {
					listeners : {
						"changecomplete" : function(slider) {
							doCalculate();
						}
					},
					decimalPrecision : 2
				}
			}, {
				allowBlank : false,
				fieldLabel : 'Amortization Period',
				width : 150,
				value : 20,
				increment : 1,
				minValue : 1,
				maxValue : 35,
				xtype : 'sliderfield',
				name : 'amortizationPeriod',
				tipText : function(thumb) {
					return String(thumb.value) + 'years';
				},
				sliderConfig : {
					listeners : {
						"changecomplete" : function(slider) {
							doCalculate();
						}
					}
				}
			}, {
				fieldLabel : 'Total Mortgage Amount',
				width : 150,
				value : 0,
				increment : 500,
				minValue : 0,
				maxValue : 1000000,
				xtype : 'sliderfield',
				name : 'mortgageAmount',
				tipText : function(thumb) {
					return Ext.util.Format.usMoney(thumb.value);
				},
				sliderConfig : {
					listeners : {
						"changecomplete" : function(slider) {
							doCalculate();
						}
					},
					decimalPrecision : 2
				}
			}, {
				fieldLabel : 'Mortgage Payment',
				width : 150,
				value : 0,
				increment : 50,
				minValue : 0,
				maxValue : 10000,
				xtype : 'sliderfield',
				name : 'mortgagePayment',
				tipText : function(thumb) {
					return Ext.util.Format.usMoney(thumb.value);
				},
				sliderConfig : {
					listeners : {
						"changecomplete" : function(slider, value) {
							if (value > 6000) {
								var form = Ext.getCmp("interestRateForm").getForm();
								form.findField("mortgagePayment").setValue(6000);
							}
							doCalculate("mortgagePayment");
						}
					},
					decimalPrecision : 2
				}
			}, new Ext.form.ComboBox({
						fieldLabel : 'Locate Us',
						store : new Ext.data.SimpleStore({
									fields : ['geoCodeAddr', 'display'],
									data : UBA.dropdowns['banches'].values
								}),
						displayField : 'display',
						mode : 'local',
						editable : false,
						triggerAction : 'all',
						emptyText : getword('select_banches'),
						selectOnFocus : true,
						width : 148,
						x : 0,
						y : 15,
						listeners : {
							"select" : function(combo, record, index) {
								var addr = record.get("geoCodeAddr");
								var display = record.get("display");
								var p = display.lastIndexOf(";");
								if (p > 0) {
									display = display.substring(p + 1);
									combo.setRawValue(display);
								}
								if (addr) {
									var win = new Ext.Window({
												layout : 'fit',
												title : 'GMap Window',
												closeAction : 'close',
												width : 400,
												height : 400,
												items : {
													xtype : 'gmappanel',
													region : 'center',
													zoomLevel : 14,
													gmapType : 'map',
													mapConfOpts : ['enableScrollWheelZoom', 'enableDoubleClickZoom', 'enableDragging'],
													mapControls : ['GSmallMapControl', 'GMapTypeControl', 'NonExistantControl'],
													setCenter : {
														geoCodeAddr : addr,
														marker : {
															title : display
														}
													}
												}
											});
									win.show();
								}
							}
						}
					}),{
				xtype : 'panel',
				border : false ,
				html : '<br /><br />The calculation is for reference only. You may contact any of our branches for more detailed and accurate information.'
			}]
};

var calculatorConfig = {
	xtype : 'button',
	iconCls : 'calculator',
	width : 100,
	height : 85,
	scale : '64',
	iconAlign : 'top',
	id : 'demo_cms_win_btn',
	text : '金融计算器窗口',
	handler : function() {
		var button = Ext.getCmp('demo_cms_win_btn');
		var win = Ext.getCmp('demo_cms_win_win');

		if (!win) {
			win = new Ext.Window({
						title : 'Mortgage Calculator',
						id : 'demo_cms_win_win',
						maximizable : false,
						width : 550,
						height : 450,
						closeAction : 'hide',
						resizable : false,
						items : calculatorItems,
						buttons : [{
									text : 'send',
									handler : function() {
									}
								}, {
									text : 'close',
									handler : function() {
										win.hide();
									}
								}]
					});
		}
		win.show(button);
	}

};

function publicInit() {
	// 这里之前做了转换一般下拉到ext下拉的程序，现在取消了。

	// 全球主站查找框，广海添加
	var globalsearch = new Ext.Panel({
				autoHeight : true,
				autoWidth : true,
				layout : 'border',
				border : false,
				items : [new Ext.form.TriggerField({
							id : 'query',
							hideTrigger : false,
							width : 110,
							region : 'center',
							selectOnFocus : true,
							text : 'Search',
							triggerClass : 'x-form-search-trigger',
							onTriggerClick : function() {
								val = this.getValue();
								if (val == '' || val == 'Search') {
									Ext.MessageBox.alert(getword('search_msg_title'), getword('search_msg_content'), function() {
												Ext.getCmp('query').focus();
											});
								} else {
									document.getElementById('toplinks_globalsearch').submit();
								}
							},
							onFocus : function() {
								val = this.getValue();
								if (val == 'Search') {
									this.setValue('');
								}
							},
							onBlur : function() {
								val = this.getValue();
								if (val == '') {
									this.setValue('Search');
								}
							}
						})]
			}).render('globalsearchdiv');
	var last_query_str = document.getElementById('last_query_str').value;
	Ext.getCmp('query').setValue(last_query_str);

}

function getMenuNavigator() {
	// 返回浏览框

	var appendChildFromAttributes = function(node) {

		if (node.childNodes.length > 0) {
			for (var k = 0; k < node.childNodes.length; k++) {
				appendChildFromAttributes(node.childNodes[k]);
			}
			node.leaf = false;
			node.attributes.leaf = false;
			node.loaded = true;
		} else if (node.attributes.children) {
			if (node.childNodes.length < 1) {
				var loader = node.loader || node.attributes.loader || node.getOwnerTree().getLoader();
				var cs = node.attributes.children;
				node.beginUpdate();
				for (var i = 0, len = cs.length; i < len; i++) {
					var cn = node.appendChild(loader.createNode(cs[i]));
					appendChildFromAttributes(cn);
				}
				node.endUpdate();
			}
			node.leaf = false;
			node.attributes.leaf = false;
			node.loaded = true;
		}

	};
	var treePanel = new Ext.tree.TreePanel({
				style : {
					'padding-bottom' : '4px'
				},
				// height : 250,
				title : CMS.activeLevel1Menu || CMS.presentMenu['wording_' + language],
				collapseMode : 'mini',
				split : true,
				minSize : 175,
				maxSize : 400,
				collapsible : true,
				rootVisible : false,
				lines : false,
				autoScroll : true,
				root : new Ext.tree.AsyncTreeNode("TreeView"),
				collapseFirst : false,
				listeners : {
					"render" : function(tree) {
						tree.root.eachChild(function(cs) {
									cs.remove();
								});
						tree.root.attributes.children = CMS.activeTopSubMenus;
						// tree.root.reload();
						appendChildFromAttributes(tree.root);
					}
				}
			});

	return treePanel;
}

function getMainHeight() {
	// 根据客户的要求，规定下部，中间滑动，这里获得中间区域的高度。。。
	var myHeight = 0;
	if (typeof(window.innerHeight) == 'number') {
		myHeight = window.innerHeight;
	} else if (document.documentElement && document.documentElement.clientHeight) {
		// IE 6+ in 'standards compliant mode'
		myHeight = document.documentElement.clientHeight;
	} else if (document.body && document.body.clientHeight) {
		// IE 4 compatible
		myHeight = document.body.clientHeight;
	}
	return myHeight - 110 - 115 > 525 ? myHeight - 110 - 115 : 525;
	
	
}

function readCookie(name) {
	var cookieValue = "";
	var search = name + "=";
	if (document.cookie.length > 0) {
		offset = document.cookie.indexOf(search);
		if (offset != -1) {
			offset += search.length;
			end = document.cookie.indexOf(";", offset);
			if (end == -1)
				end = document.cookie.length;
			cookieValue = unescape(document.cookie.substring(offset, end))
		}
	}
	return cookieValue;
}



