+var combo = new Ext.form.ComboBox({
+ ...
+ mode: 'remote',
+ ...
+ listeners: {
+ // delete the previous query in the beforequery event or set
+ // combo.lastQuery = null (this will reload the store the next time it expands)
+ beforequery: function(qe){
+ delete qe.combo.lastQuery;
+ }
+ }
+});
+ * </code></pre>
+ * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
+ * configure the combo with <tt>lastQuery=''</tt>. Example use:
+ * <pre><code>
+var combo = new Ext.form.ComboBox({
+ ...
+ mode: 'local',
+ triggerAction: 'all',
+ lastQuery: ''
+});
+ * </code></pre>
+ * @property lastQuery
+ * @type String
+ */
+
+ // private
+ initComponent : function(){
+ Ext.form.ComboBox.superclass.initComponent.call(this);
+ this.addEvents(
+ /**
+ * @event expand
+ * Fires when the dropdown list is expanded
+ * @param {Ext.form.ComboBox} combo This combo box
+ */
+ 'expand',
+ /**
+ * @event collapse
+ * Fires when the dropdown list is collapsed
+ * @param {Ext.form.ComboBox} combo This combo box
+ */
+ 'collapse',
+
+ /**
+ * @event beforeselect
+ * Fires before a list item is selected. Return false to cancel the selection.
+ * @param {Ext.form.ComboBox} combo This combo box
+ * @param {Ext.data.Record} record The data record returned from the underlying store
+ * @param {Number} index The index of the selected item in the dropdown list
+ */
+ 'beforeselect',
+ /**
+ * @event select
+ * Fires when a list item is selected
+ * @param {Ext.form.ComboBox} combo This combo box
+ * @param {Ext.data.Record} record The data record returned from the underlying store
+ * @param {Number} index The index of the selected item in the dropdown list
+ */
+ 'select',
+ /**
+ * @event beforequery
+ * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
+ * cancel property to true.
+ * @param {Object} queryEvent An object that has these properties:<ul>
+ * <li><code>combo</code> : Ext.form.ComboBox <div class="sub-desc">This combo box</div></li>
+ * <li><code>query</code> : String <div class="sub-desc">The query</div></li>
+ * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
+ * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
+ * </ul>
+ */
+ 'beforequery'
+ );
+ if(this.transform){
+ var s = Ext.getDom(this.transform);
+ if(!this.hiddenName){
+ this.hiddenName = s.name;
+ }
+ 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],
+ value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text;
+ if(o.selected && Ext.isEmpty(this.value, true)) {
+ this.value = value;
+ }
+ d.push([value, o.text]);
+ }
+ this.store = new Ext.data.ArrayStore({
+ 'id': 0,
+ fields: ['value', 'text'],
+ data : d,
+ autoDestroy: true
+ });
+ this.valueField = 'value';
+ this.displayField = 'text';
+ }
+ s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference
+ if(!this.lazyRender){
+ this.target = true;
+ this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
+ this.render(this.el.parentNode, s);
+ }
+ Ext.removeNode(s);
+ }
+ //auto-configure store from local array data
+ else if(this.store){
+ this.store = Ext.StoreMgr.lookup(this.store);
+ if(this.store.autoCreated){
+ this.displayField = this.valueField = 'field1';
+ if(!this.store.expandData){
+ this.displayField = 'field2';
+ }
+ this.mode = 'local';
+ }
+ }
+
+ this.selectedIndex = -1;
+ if(this.mode == 'local'){
+ if(!Ext.isDefined(this.initialConfig.queryDelay)){
+ this.queryDelay = 10;
+ }
+ if(!Ext.isDefined(this.initialConfig.minChars)){
+ this.minChars = 0;
+ }
+ }
+ },
+
+ // private
+ onRender : function(ct, position){
+ if(this.hiddenName && !Ext.isDefined(this.submitValue)){
+ this.submitValue = false;
+ }
+ Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
+ if(this.hiddenName){
+ this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
+ id: (this.hiddenId||this.hiddenName)}, 'before', true);
+
+ }
+ if(Ext.isGecko){
+ this.el.dom.setAttribute('autocomplete', 'off');
+ }
+
+ if(!this.lazyInit){
+ this.initList();
+ }else{
+ this.on('focus', this.initList, this, {single: true});
+ }
+ },
+
+ // private
+ initValue : function(){
+ Ext.form.ComboBox.superclass.initValue.call(this);
+ if(this.hiddenField){
+ this.hiddenField.value =
+ Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, '');
+ }
+ },
+
+ getParentZIndex : function(){
+ var zindex;
+ if (this.ownerCt){
+ this.findParentBy(function(ct){
+ zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
+ return !!zindex;
+ });
+ }
+ return zindex;
+ },
+
+ // private
+ initList : function(){
+ if(!this.list){
+ var cls = 'x-combo-list',
+ listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
+ zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
+
+ if (!zindex) {
+ zindex = this.getParentZIndex();
+ }
+
+ this.list = new Ext.Layer({
+ parentEl: listParent,
+ shadow: this.shadow,
+ cls: [cls, this.listClass].join(' '),
+ constrain:false,
+ zindex: (zindex || 12000) + 5
+ });
+
+ var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
+ this.list.setSize(lw, 0);
+ this.list.swallowEvent('mousewheel');
+ this.assetHeight = 0;
+ if(this.syncFont !== false){
+ this.list.setStyle('font-size', this.el.getStyle('font-size'));
+ }
+ if(this.title){
+ this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
+ this.assetHeight += this.header.getHeight();
+ }
+
+ this.innerList = this.list.createChild({cls:cls+'-inner'});
+ this.mon(this.innerList, 'mouseover', this.onViewOver, this);
+ this.mon(this.innerList, 'mousemove', this.onViewMove, this);
+ this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
+
+ if(this.pageSize){
+ this.footer = this.list.createChild({cls:cls+'-ft'});
+ this.pageTb = new Ext.PagingToolbar({
+ store: this.store,
+ pageSize: this.pageSize,
+ renderTo:this.footer
+ });
+ this.assetHeight += this.footer.getHeight();
+ }
+
+ if(!this.tpl){
+ /**
+ * @cfg {String/Ext.XTemplate} tpl <p>The template string, or {@link Ext.XTemplate} instance to
+ * use to display each item in the dropdown list. The dropdown list is displayed in a
+ * DataView. See {@link #view}.</p>
+ * <p>The default template string is:</p><pre><code>
+ '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
+ * </code></pre>
+ * <p>Override the default value to create custom UI layouts for items in the list.
+ * For example:</p><pre><code>
+ '<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
+ * </code></pre>
+ * <p>The template <b>must</b> contain one or more substitution parameters using field
+ * names from the Combo's</b> {@link #store Store}. In the example above an
+ * <pre>ext:qtip</pre> attribute is added to display other fields from the Store.</p>
+ * <p>To preserve the default visual look of list items, add the CSS class name
+ * <pre>x-combo-list-item</pre> to the template's container element.</p>
+ * <p>Also see {@link #itemSelector} for additional details.</p>
+ */
+ this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
+ /**
+ * @cfg {String} itemSelector
+ * <p>A simple CSS selector (e.g. div.some-class or span:first-child) that will be
+ * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown
+ * display will be working with.</p>
+ * <p><b>Note</b>: this setting is <b>required</b> if a custom XTemplate has been
+ * specified in {@link #tpl} which assigns a class other than <pre>'x-combo-list-item'</pre>
+ * to dropdown list items</b>
+ */
+ }
+
+ /**
+ * The {@link Ext.DataView DataView} used to display the ComboBox's options.
+ * @type Ext.DataView
+ */
+ this.view = new Ext.DataView({
+ applyTo: this.innerList,
+ tpl: this.tpl,
+ singleSelect: true,
+ selectedClass: this.selectedClass,
+ itemSelector: this.itemSelector || '.' + cls + '-item',
+ emptyText: this.listEmptyText,
+ deferEmptyText: false
+ });
+
+ this.mon(this.view, {
+ containerclick : this.onViewClick,
+ click : this.onViewClick,
+ scope :this
+ });
+
+ this.bindStore(this.store, true);
+
+ if(this.resizable){
+ this.resizer = new Ext.Resizable(this.list, {
+ pinned:true, handles:'se'
+ });
+ this.mon(this.resizer, 'resize', function(r, w, h){
+ this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
+ this.listWidth = w;
+ this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
+ this.restrictHeight();
+ }, this);
+
+ this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
+ }
+ }
+ },
+
+ /**
+ * <p>Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.</p>
+ * A custom implementation may be provided as a configuration option if the floating list needs to be rendered
+ * to a different Element. An example might be rendering the list inside a Menu so that clicking
+ * the list does not hide the Menu:<pre><code>
+var store = new Ext.data.ArrayStore({
+ autoDestroy: true,
+ fields: ['initials', 'fullname'],
+ data : [
+ ['FF', 'Fred Flintstone'],
+ ['BR', 'Barney Rubble']
+ ]
+});
+
+var combo = new Ext.form.ComboBox({
+ store: store,
+ displayField: 'fullname',
+ emptyText: 'Select a name...',
+ forceSelection: true,
+ getListParent: function() {
+ return this.el.up('.x-menu');
+ },
+ iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
+ mode: 'local',
+ selectOnFocus: true,
+ triggerAction: 'all',
+ typeAhead: true,
+ width: 135
+});
+
+var menu = new Ext.menu.Menu({
+ id: 'mainMenu',
+ items: [
+ combo // A Field in a Menu
+ ]
+});
+</code></pre>
+ */
+ getListParent : function() {
+ return document.body;
+ },
+
+ /**
+ * Returns the store associated with this combo.
+ * @return {Ext.data.Store} The store
+ */
+ getStore : function(){
+ return this.store;
+ },
+
+ // private
+ bindStore : function(store, initial){
+ if(this.store && !initial){
+ if(this.store !== store && this.store.autoDestroy){
+ this.store.destroy();
+ }else{
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('exception', this.collapse, this);
+ }
+ if(!store){
+ this.store = null;
+ if(this.view){
+ this.view.bindStore(null);
+ }
+ if(this.pageTb){
+ this.pageTb.bindStore(null);
+ }
+ }
+ }
+ if(store){
+ if(!initial) {
+ this.lastQuery = null;
+ if(this.pageTb) {
+ this.pageTb.bindStore(store);
+ }
+ }
+
+ this.store = Ext.StoreMgr.lookup(store);
+ this.store.on({
+ scope: this,
+ beforeload: this.onBeforeLoad,
+ load: this.onLoad,
+ exception: this.collapse
+ });
+
+ if(this.view){
+ this.view.bindStore(store);
+ }
+ }
+ },
+
+ reset : function(){
+ Ext.form.ComboBox.superclass.reset.call(this);
+ if(this.clearFilterOnReset && this.mode == 'local'){
+ this.store.clearFilter();
+ }
+ },
+
+ // private
+ initEvents : function(){
+ Ext.form.ComboBox.superclass.initEvents.call(this);
+
+ /**
+ * @property keyNav
+ * @type Ext.KeyNav
+ * <p>A {@link Ext.KeyNav KeyNav} object which handles navigation keys for this ComboBox. This performs actions
+ * based on keystrokes typed when the input field is focused.</p>
+ * <p><b>After the ComboBox has been rendered</b>, you may override existing navigation key functionality,
+ * or add your own based upon key names as specified in the {@link Ext.KeyNav KeyNav} class.</p>
+ * <p>The function is executed in the scope (<code>this</code> reference of the ComboBox. Example:</p><pre><code>
+myCombo.keyNav.esc = function(e) { // Override ESC handling function
+ this.collapse(); // Standard behaviour of Ext's ComboBox.
+ this.setValue(this.startValue); // We reset to starting value on ESC
+};
+myCombo.keyNav.tab = function() { // Override TAB handling function
+ this.onViewClick(false); // Select the currently highlighted row
+};
+</code></pre>
+ */
+ this.keyNav = new Ext.KeyNav(this.el, {
+ "up" : function(e){
+ this.inKeyMode = true;
+ this.selectPrev();
+ },
+
+ "down" : function(e){
+ if(!this.isExpanded()){
+ this.onTriggerClick();
+ }else{
+ this.inKeyMode = true;
+ this.selectNext();
+ }
+ },
+
+ "enter" : function(e){
+ this.onViewClick();
+ },
+
+ "esc" : function(e){
+ this.collapse();
+ },
+
+ "tab" : function(e){
+ if (this.forceSelection === true) {
+ this.collapse();
+ } else {
+ this.onViewClick(false);
+ }
+ return true;
+ },
+
+ scope : this,
+
+ doRelay : function(e, h, hname){
+ if(hname == 'down' || this.scope.isExpanded()){
+ // this MUST be called before ComboBox#fireKey()
+ var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments);
+ if(!Ext.isIE && Ext.EventManager.useKeydown){
+ // call Combo#fireKey() for browsers which use keydown event (except IE)
+ this.scope.fireKey(e);
+ }
+ return relay;
+ }
+ return true;
+ },
+
+ forceKeyDown : true,
+ defaultEventAction: 'stopEvent'
+ });
+ this.queryDelay = Math.max(this.queryDelay || 10,
+ this.mode == 'local' ? 10 : 250);
+ this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
+ if(this.typeAhead){
+ this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
+ }
+ if(!this.enableKeyEvents){
+ this.mon(this.el, 'keyup', this.onKeyUp, this);
+ }
+ },
+
+
+ // private
+ onDestroy : function(){
+ if (this.dqTask){
+ this.dqTask.cancel();
+ this.dqTask = null;
+ }
+ this.bindStore(null);
+ Ext.destroy(
+ this.resizer,
+ this.view,
+ this.pageTb,
+ this.list
+ );
+ Ext.destroyMembers(this, 'hiddenField');
+ Ext.form.ComboBox.superclass.onDestroy.call(this);
+ },
+
+ // private
+ fireKey : function(e){
+ if (!this.isExpanded()) {
+ Ext.form.ComboBox.superclass.fireKey.call(this, e);
+ }
+ },
+
+ // private
+ onResize : function(w, h){
+ Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
+ if(!isNaN(w) && this.isVisible() && this.list){
+ this.doResize(w);
+ }else{
+ this.bufferSize = w;
+ }
+ },
+
+ doResize: function(w){
+ if(!Ext.isDefined(this.listWidth)){
+ var lw = Math.max(w, this.minListWidth);
+ this.list.setWidth(lw);
+ this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
+ }
+ },
+
+ // private
+ onEnable : function(){
+ Ext.form.ComboBox.superclass.onEnable.apply(this, arguments);
+ if(this.hiddenField){
+ this.hiddenField.disabled = false;
+ }
+ },
+
+ // private
+ onDisable : function(){
+ Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
+ if(this.hiddenField){
+ this.hiddenField.disabled = true;
+ }
+ },
+
+ // private
+ onBeforeLoad : function(){
+ if(!this.hasFocus){
+ return;
+ }
+ this.innerList.update(this.loadingText ?
+ '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
+ this.restrictHeight();
+ this.selectedIndex = -1;
+ },
+
+ // private
+ onLoad : function(){
+ if(!this.hasFocus){
+ return;
+ }
+ if(this.store.getCount() > 0 || this.listEmptyText){
+ this.expand();
+ this.restrictHeight();
+ if(this.lastQuery == this.allQuery){
+ if(this.editable){
+ this.el.dom.select();
+ }
+
+ if(this.autoSelect !== false && !this.selectByValue(this.value, true)){
+ this.select(0, true);
+ }
+ }else{
+ if(this.autoSelect !== false){
+ this.selectNext();
+ }
+ if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
+ this.taTask.delay(this.typeAheadDelay);
+ }
+ }
+ }else{
+ this.collapse();
+ }
+
+ },
+
+ // private
+ onTypeAhead : function(){
+ if(this.store.getCount() > 0){
+ var r = this.store.getAt(0);
+ var newValue = r.data[this.displayField];
+ var len = newValue.length;
+ var selStart = this.getRawValue().length;
+ if(selStart != len){
+ this.setRawValue(newValue);
+ this.selectText(selStart, newValue.length);
+ }
+ }
+ },
+
+ // private
+ assertValue : function(){
+ var val = this.getRawValue(),
+ rec = this.findRecord(this.displayField, val);
+
+ if(!rec && this.forceSelection){
+ if(val.length > 0 && val != this.emptyText){
+ this.el.dom.value = Ext.value(this.lastSelectionText, '');
+ this.applyEmptyText();
+ }else{
+ this.clearValue();
+ }
+ }else{
+ if(rec){
+ // onSelect may have already set the value and by doing so
+ // set the display field properly. Let's not wipe out the
+ // valueField here by just sending the displayField.
+ if (val == rec.get(this.displayField) && this.value == rec.get(this.valueField)){
+ return;
+ }
+ val = rec.get(this.valueField || this.displayField);
+ }
+ this.setValue(val);
+ }
+ },
+
+ // private
+ onSelect : function(record, index){
+ if(this.fireEvent('beforeselect', this, record, index) !== false){
+ this.setValue(record.data[this.valueField || this.displayField]);
+ this.collapse();
+ this.fireEvent('select', this, record, index);
+ }
+ },
+
+ // inherit docs
+ getName: function(){
+ var hf = this.hiddenField;
+ return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this);
+ },
+
+ /**
+ * Returns the currently selected field value or empty string if no value is set.
+ * @return {String} value The selected value
+ */
+ getValue : function(){
+ if(this.valueField){
+ return Ext.isDefined(this.value) ? this.value : '';
+ }else{
+ return Ext.form.ComboBox.superclass.getValue.call(this);
+ }
+ },
+
+ /**
+ * Clears any text/value currently set in the field
+ */
+ clearValue : function(){
+ if(this.hiddenField){
+ this.hiddenField.value = '';
+ }
+ this.setRawValue('');
+ this.lastSelectionText = '';
+ this.applyEmptyText();
+ this.value = '';
+ },
+
+ /**
+ * Sets the specified value into the field. If the value finds a match, the corresponding record text
+ * will be displayed in the field. If the value does not match the data value of an existing item,
+ * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
+ * Otherwise the field will be blank (although the value will still be set).
+ * @param {String} value The value to match
+ * @return {Ext.form.Field} this
+ */
+ setValue : function(v){
+ var text = v;
+ if(this.valueField){
+ var r = this.findRecord(this.valueField, v);
+ if(r){
+ text = r.data[this.displayField];
+ }else if(Ext.isDefined(this.valueNotFoundText)){
+ text = this.valueNotFoundText;
+ }
+ }
+ this.lastSelectionText = text;
+ if(this.hiddenField){
+ this.hiddenField.value = Ext.value(v, '');
+ }
+ Ext.form.ComboBox.superclass.setValue.call(this, text);
+ this.value = v;
+ return this;
+ },
+
+ // private
+ findRecord : function(prop, value){
+ var record;
+ if(this.store.getCount() > 0){
+ this.store.each(function(r){
+ if(r.data[prop] == value){
+ record = r;
+ return false;
+ }
+ });
+ }
+ return record;
+ },
+
+ // private
+ onViewMove : function(e, t){
+ this.inKeyMode = false;
+ },
+
+ // private
+ onViewOver : function(e, t){
+ if(this.inKeyMode){ // prevent key nav and mouse over conflicts
+ return;
+ }
+ var item = this.view.findItemFromChild(t);
+ if(item){
+ var index = this.view.indexOf(item);
+ this.select(index, false);
+ }
+ },
+
+ // private
+ onViewClick : function(doFocus){
+ var index = this.view.getSelectedIndexes()[0],
+ s = this.store,
+ r = s.getAt(index);
+ if(r){
+ this.onSelect(r, index);
+ }else {
+ this.collapse();
+ }
+ if(doFocus !== false){
+ this.el.focus();
+ }
+ },
+
+
+ // private
+ restrictHeight : function(){
+ this.innerList.dom.style.height = '';
+ var inner = this.innerList.dom,
+ pad = this.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)-this.list.shadowOffset-pad-5;
+
+ h = Math.min(h, space, this.maxHeight);
+
+ this.innerList.setHeight(h);
+ this.list.beginUpdate();
+ this.list.setHeight(h+pad);
+ this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
+ this.list.endUpdate();
+ },
+
+ /**
+ * Returns true if the dropdown list is expanded, else false.
+ */
+ isExpanded : function(){
+ return this.list && this.list.isVisible();
+ },
+
+ /**
+ * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
+ * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
+ * @param {String} value The data value of the item to select
+ * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
+ * selected item if it is not currently in view (defaults to true)
+ * @return {Boolean} True if the value matched an item in the list, else false
+ */
+ selectByValue : function(v, scrollIntoView){
+ if(!Ext.isEmpty(v, true)){
+ var r = this.findRecord(this.valueField || this.displayField, v);
+ if(r){
+ this.select(this.store.indexOf(r), scrollIntoView);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
+ * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
+ * @param {Number} index The zero-based index of the list item to select
+ * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
+ * selected item if it is not currently in view (defaults to true)
+ */
+ select : function(index, scrollIntoView){
+ this.selectedIndex = index;
+ this.view.select(index);
+ if(scrollIntoView !== false){
+ var el = this.view.getNode(index);
+ if(el){
+ this.innerList.scrollChildIntoView(el, false);
+ }
+ }
+
+ },
+
+ // private
+ selectNext : function(){
+ var ct = this.store.getCount();
+ if(ct > 0){
+ if(this.selectedIndex == -1){
+ this.select(0);
+ }else if(this.selectedIndex < ct-1){
+ this.select(this.selectedIndex+1);
+ }
+ }
+ },
+
+ // private
+ selectPrev : function(){
+ var ct = this.store.getCount();
+ if(ct > 0){
+ if(this.selectedIndex == -1){
+ this.select(0);
+ }else if(this.selectedIndex !== 0){
+ this.select(this.selectedIndex-1);
+ }
+ }
+ },
+
+ // private
+ onKeyUp : function(e){
+ var k = e.getKey();
+ if(this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())){
+
+ this.lastKey = k;
+ this.dqTask.delay(this.queryDelay);
+ }
+ Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
+ },
+
+ // private
+ validateBlur : function(){
+ return !this.list || !this.list.isVisible();
+ },
+
+ // private
+ initQuery : function(){
+ this.doQuery(this.getRawValue());
+ },
+
+ // private
+ beforeBlur : function(){
+ this.assertValue();
+ },
+
+ // private
+ postBlur : function(){
+ Ext.form.ComboBox.superclass.postBlur.call(this);
+ this.collapse();
+ this.inKeyMode = false;
+ },
+
+ /**
+ * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
+ * query allowing the query action to be canceled if needed.
+ * @param {String} query The SQL query to execute
+ * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
+ * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option. It
+ * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
+ */
+ doQuery : function(q, forceAll){
+ 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)){
+ if(this.lastQuery !== q){
+ 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.load({
+ params: this.getParams(q)
+ });
+ this.expand();
+ }
+ }else{
+ this.selectedIndex = -1;
+ this.onLoad();
+ }
+ }
+ },
+
+ // private
+ getParams : function(q){
+ var p = {};
+ //p[this.queryParam] = q;
+ if(this.pageSize){
+ p.start = 0;
+ p.limit = this.pageSize;
+ }
+ return p;
+ },
+
+ /**
+ * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion.
+ */
+ collapse : function(){
+ if(!this.isExpanded()){
+ return;
+ }
+ this.list.hide();
+ Ext.getDoc().un('mousewheel', this.collapseIf, this);
+ Ext.getDoc().un('mousedown', this.collapseIf, this);
+ this.fireEvent('collapse', this);
+ },
+
+ // private
+ collapseIf : function(e){
+ if(!this.isDestroyed && !e.within(this.wrap) && !e.within(this.list)){
+ this.collapse();
+ }
+ },
+
+ /**
+ * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion.
+ */
+ expand : function(){
+ if(this.isExpanded() || !this.hasFocus){
+ return;
+ }
+
+ if(this.title || this.pageSize){
+ this.assetHeight = 0;
+ if(this.title){
+ this.assetHeight += this.header.getHeight();
+ }
+ if(this.pageSize){
+ this.assetHeight += this.footer.getHeight();
+ }
+ }
+
+ if(this.bufferSize){
+ this.doResize(this.bufferSize);
+ delete this.bufferSize;
+ }
+ this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
+
+ // zindex can change, re-check it and set it if necessary
+ var listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
+ zindex = parseInt(Ext.fly(listParent).getStyle('z-index') ,10);
+ if (!zindex){
+ zindex = this.getParentZIndex();
+ }
+ if (zindex) {
+ this.list.setZIndex(zindex + 5);
+ }
+ this.list.show();
+ if(Ext.isGecko2){
+ this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
+ }
+ this.mon(Ext.getDoc(), {
+ scope: this,
+ mousewheel: this.collapseIf,
+ mousedown: this.collapseIf
+ });
+ this.fireEvent('expand', this);
+ },
+
+ /**
+ * @method onTriggerClick
+ * @hide
+ */
+ // private
+ // Implements the default empty TriggerField.onTriggerClick function
+ onTriggerClick : function(){
+ if(this.readOnly || this.disabled){
+ return;
+ }
+ if(this.isExpanded()){
+ this.collapse();
+ this.el.focus();
+ }else {
+ this.onFocus({});
+ if(this.triggerAction == 'all') {
+ this.doQuery(this.allQuery, true);
+ } else {
+ this.doQuery(this.getRawValue());
+ }
+ this.el.focus();
+ }
+ }
+
+ /**
+ * @hide
+ * @method autoSize
+ */
+ /**
+ * @cfg {Boolean} grow @hide
+ */
+ /**
+ * @cfg {Number} growMin @hide
+ */
+ /**
+ * @cfg {Number} growMax @hide
+ */
+
+});
+Ext.reg('combo', Ext.form.ComboBox);
+/**
+ * @class Ext.form.Checkbox
+ * @extends Ext.form.Field
+ * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields.
+ * @constructor
+ * Creates a new Checkbox
+ * @param {Object} config Configuration options
+ * @xtype checkbox
+ */
+Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
+ /**
+ * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
+ */
+ focusClass : undefined,
+ /**
+ * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to 'x-form-field')
+ */
+ fieldClass : 'x-form-field',
+ /**
+ * @cfg {Boolean} checked <tt>true</tt> if the checkbox should render initially checked (defaults to <tt>false</tt>)
+ */
+ checked : false,
+ /**
+ * @cfg {String} boxLabel The text that appears beside the checkbox
+ */
+ boxLabel: ' ',
+ /**
+ * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
+ * {tag: 'input', type: 'checkbox', autocomplete: 'off'})
+ */
+ defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'},
+ /**
+ * @cfg {String} boxLabel The text that appears beside the checkbox
+ */
+ /**
+ * @cfg {String} inputValue The value that should go into the generated input element's value attribute
+ */
+ /**
+ * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
+ * handling the check event). The handler is passed the following parameters:
+ * <div class="mdetail-params"><ul>
+ * <li><b>checkbox</b> : Ext.form.Checkbox<div class="sub-desc">The Checkbox being toggled.</div></li>
+ * <li><b>checked</b> : Boolean<div class="sub-desc">The new checked state of the checkbox.</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function
+ * (defaults to this Checkbox).
+ */
+
+ // private
+ actionMode : 'wrap',
+
+ // private
+ initComponent : function(){
+ Ext.form.Checkbox.superclass.initComponent.call(this);
+ this.addEvents(
+ /**
+ * @event check
+ * Fires when the checkbox is checked or unchecked.
+ * @param {Ext.form.Checkbox} this This checkbox
+ * @param {Boolean} checked The new checked value
+ */
+ 'check'
+ );
+ },
+
+ // private
+ onResize : function(){
+ Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
+ if(!this.boxLabel && !this.fieldLabel){
+ this.el.alignTo(this.wrap, 'c-c');
+ }
+ },
+
+ // private
+ initEvents : function(){
+ Ext.form.Checkbox.superclass.initEvents.call(this);
+ this.mon(this.el, {
+ scope: this,
+ click: this.onClick,
+ change: this.onClick
+ });
+ },
+
+ /**
+ * @hide
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking.
+ * @method
+ */
+ markInvalid : Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking.
+ * @method
+ */
+ clearInvalid : Ext.emptyFn,
+
+ // private
+ onRender : function(ct, position){
+ Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
+ if(this.inputValue !== undefined){
+ this.el.dom.value = this.inputValue;
+ }
+ this.wrap = this.el.wrap({cls: 'x-form-check-wrap'});
+ if(this.boxLabel){
+ this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
+ }
+ if(this.checked){
+ this.setValue(true);
+ }else{
+ this.checked = this.el.dom.checked;
+ }
+ // Need to repaint for IE, otherwise positioning is broken
+ if(Ext.isIE){
+ this.wrap.repaint();
+ }
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+ // private
+ onDestroy : function(){
+ Ext.destroy(this.wrap);
+ Ext.form.Checkbox.superclass.onDestroy.call(this);
+ },
+
+ // private
+ initValue : function() {
+ this.originalValue = this.getValue();
+ },
+
+ /**
+ * Returns the checked state of the checkbox.
+ * @return {Boolean} True if checked, else false
+ */
+ getValue : function(){
+ if(this.rendered){
+ return this.el.dom.checked;
+ }
+ return this.checked;
+ },
+
+ // private
+ onClick : function(){
+ if(this.el.dom.checked != this.checked){
+ this.setValue(this.el.dom.checked);
+ }
+ },
+
+ /**
+ * Sets the checked state of the checkbox, fires the 'check' event, and calls a
+ * <code>{@link #handler}</code> (if configured).
+ * @param {Boolean/String} checked The following values will check the checkbox:
+ * <code>true, 'true', '1', or 'on'</code>. Any other value will uncheck the checkbox.
+ * @return {Ext.form.Field} this
+ */
+ setValue : function(v){
+ var checked = this.checked ;
+ this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
+ if(this.rendered){
+ this.el.dom.checked = this.checked;
+ this.el.dom.defaultChecked = this.checked;
+ }
+ if(checked != this.checked){
+ this.fireEvent('check', this, this.checked);
+ if(this.handler){
+ this.handler.call(this.scope || this, this, this.checked);
+ }
+ }
+ return this;
+ }
+});
+Ext.reg('checkbox', Ext.form.Checkbox);
+/**
+ * @class Ext.form.CheckboxGroup
+ * @extends Ext.form.Field
+ * <p>A grouping container for {@link Ext.form.Checkbox} controls.</p>
+ * <p>Sample usage:</p>
+ * <pre><code>
+var myCheckboxGroup = new Ext.form.CheckboxGroup({
+ id:'myGroup',
+ xtype: 'checkboxgroup',
+ fieldLabel: 'Single Column',
+ itemCls: 'x-check-group-alt',
+ // Put all controls in a single column with width 100%
+ columns: 1,
+ items: [
+ {boxLabel: 'Item 1', name: 'cb-col-1'},
+ {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
+ {boxLabel: 'Item 3', name: 'cb-col-3'}
+ ]
+});
+ * </code></pre>
+ * @constructor
+ * Creates a new CheckboxGroup
+ * @param {Object} config Configuration options
+ * @xtype checkboxgroup
+ */
+Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
+ /**
+ * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects
+ * to arrange in the group.
+ */
+ /**
+ * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped
+ * checkbox/radio controls using automatic layout. This config can take several types of values:
+ * <ul><li><b>'auto'</b> : <p class="sub-desc">The controls will be rendered one per column on one row and the width
+ * of each column will be evenly distributed based on the width of the overall field container. This is the default.</p></li>
+ * <li><b>Number</b> : <p class="sub-desc">If you specific a number (e.g., 3) that number of columns will be
+ * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.</p></li>
+ * <li><b>Array</b> : Object<p class="sub-desc">You can also specify an array of column widths, mixing integer
+ * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
+ * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float
+ * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field
+ * container you should do so.</p></li></ul>
+ */
+ columns : 'auto',
+ /**
+ * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
+ * top to bottom before starting on the next column. The number of controls in each column will be automatically
+ * calculated to keep columns as even as possible. The default value is false, so that controls will be added
+ * to columns one at a time, completely filling each row left to right before starting on the next row.
+ */
+ vertical : false,
+ /**
+ * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true).
+ * If no items are selected at validation time, {@link @blankText} will be used as the error text.
+ */
+ allowBlank : true,
+ /**
+ * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
+ * select at least one item in this group")
+ */
+ blankText : "You must select at least one item in this group",
+
+ // private
+ defaultType : 'checkbox',
+
+ // private
+ groupCls : 'x-form-check-group',
+
+ // private
+ initComponent: function(){
+ this.addEvents(
+ /**
+ * @event change
+ * Fires when the state of a child checkbox changes.
+ * @param {Ext.form.CheckboxGroup} this
+ * @param {Array} checked An array containing the checked boxes.
+ */
+ 'change'
+ );
+ this.on('change', this.validate, this);
+ Ext.form.CheckboxGroup.superclass.initComponent.call(this);
+ },
+
+ // private
+ onRender : function(ct, position){
+ if(!this.el){
+ var panelCfg = {
+ autoEl: {
+ id: this.id
+ },
+ cls: this.groupCls,
+ layout: 'column',
+ renderTo: ct,
+ bufferResize: false // Default this to false, since it doesn't really have a proper ownerCt.
+ };
+ var colCfg = {
+ xtype: 'container',
+ defaultType: this.defaultType,
+ layout: 'form',
+ defaults: {
+ hideLabel: true,
+ anchor: '100%'
+ }
+ };
+
+ if(this.items[0].items){
+
+ // The container has standard ColumnLayout configs, so pass them in directly
+
+ Ext.apply(panelCfg, {
+ layoutConfig: {columns: this.items.length},
+ defaults: this.defaults,
+ items: this.items
+ });
+ for(var i=0, len=this.items.length; i<len; i++){
+ Ext.applyIf(this.items[i], colCfg);
+ }
+
+ }else{
+
+ // The container has field item configs, so we have to generate the column
+ // panels first then move the items into the columns as needed.
+
+ var numCols, cols = [];
+
+ if(typeof this.columns == 'string'){ // 'auto' so create a col per item
+ this.columns = this.items.length;
+ }
+ if(!Ext.isArray(this.columns)){
+ var cs = [];
+ for(var i=0; i<this.columns; i++){
+ cs.push((100/this.columns)*.01); // distribute by even %
+ }
+ this.columns = cs;
+ }
+
+ numCols = this.columns.length;
+
+ // Generate the column configs with the correct width setting
+ for(var i=0; i<numCols; i++){
+ var cc = Ext.apply({items:[]}, colCfg);
+ cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = this.columns[i];
+ if(this.defaults){
+ cc.defaults = Ext.apply(cc.defaults || {}, this.defaults);
+ }
+ cols.push(cc);
+ };
+
+ // Distribute the original items into the columns
+ if(this.vertical){
+ var rows = Math.ceil(this.items.length / numCols), ri = 0;
+ for(var i=0, len=this.items.length; i<len; i++){
+ if(i>0 && i%rows==0){
+ ri++;
+ }
+ if(this.items[i].fieldLabel){
+ this.items[i].hideLabel = false;
+ }
+ cols[ri].items.push(this.items[i]);
+ };
+ }else{
+ for(var i=0, len=this.items.length; i<len; i++){
+ var ci = i % numCols;
+ if(this.items[i].fieldLabel){
+ this.items[i].hideLabel = false;
+ }
+ cols[ci].items.push(this.items[i]);
+ };
+ }
+
+ Ext.apply(panelCfg, {
+ layoutConfig: {columns: numCols},
+ items: cols
+ });
+ }
+
+ this.panel = new Ext.Container(panelCfg);
+ this.panel.ownerCt = this;
+ this.el = this.panel.getEl();
+
+ if(this.forId && this.itemCls){
+ var l = this.el.up(this.itemCls).child('label', true);
+ if(l){
+ l.setAttribute('htmlFor', this.forId);
+ }
+ }
+
+ var fields = this.panel.findBy(function(c){
+ return c.isFormField;
+ }, this);
+
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+ }
+ Ext.form.CheckboxGroup.superclass.onRender.call(this, ct, position);
+ },
+
+ initValue : function(){
+ if(this.value){
+ this.setValue.apply(this, this.buffered ? this.value : [this.value]);
+ delete this.buffered;
+ delete this.value;
+ }
+ },
+
+ afterRender : function(){
+ Ext.form.CheckboxGroup.superclass.afterRender.call(this);
+ this.eachItem(function(item){
+ item.on('check', this.fireChecked, this);
+ item.inGroup = true;
+ });
+ },
+
+ // private
+ doLayout: function(){
+ //ugly method required to layout hidden items
+ if(this.rendered){
+ this.panel.forceLayout = this.ownerCt.forceLayout;
+ this.panel.doLayout();
+ }
+ },
+
+ // private
+ fireChecked: function(){
+ var arr = [];
+ this.eachItem(function(item){
+ if(item.checked){
+ arr.push(item);
+ }
+ });
+ this.fireEvent('change', this, arr);
+ },
+
+ /**
+ * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default
+ * is if allowBlank is set to true and no items are checked.
+ * @return {Array} Array of all validation errors
+ */
+ getErrors: function() {
+ var errors = Ext.form.CheckboxGroup.superclass.getErrors.apply(this, arguments);
+
+ if (!this.allowBlank) {
+ var blank = true;
+
+ this.eachItem(function(f){
+ if (f.checked) {
+ return (blank = false);
+ }
+ });
+
+ if (blank) errors.push(this.blankText);
+ }
+
+ return errors;
+ },
+
+ // private
+ isDirty: function(){
+ //override the behaviour to check sub items.
+ if (this.disabled || !this.rendered) {
+ return false;
+ }
+
+ var dirty = false;
+
+ this.eachItem(function(item){
+ if(item.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+
+ return dirty;
+ },
+
+ // private
+ setReadOnly : function(readOnly){
+ if(this.rendered){
+ this.eachItem(function(item){
+ item.setReadOnly(readOnly);
+ });
+ }
+ this.readOnly = readOnly;
+ },
+
+ // private
+ onDisable : function(){
+ this.eachItem(function(item){
+ item.disable();
+ });
+ },
+
+ // private
+ onEnable : function(){
+ this.eachItem(function(item){
+ item.enable();
+ });
+ },
+
+ // private
+ onResize : function(w, h){
+ this.panel.setSize(w, h);
+ this.panel.doLayout();
+ },
+
+ // inherit docs from Field
+ reset : function(){
+ if (this.originalValue) {
+ // Clear all items
+ this.eachItem(function(c){
+ if(c.setValue){
+ c.setValue(false);
+ c.originalValue = c.getValue();
+ }
+ });
+ // Set items stored in originalValue, ugly - set a flag to reset the originalValue
+ // during the horrible onSetValue. This will allow trackResetOnLoad to function.
+ this.resetOriginal = true;
+ this.setValue(this.originalValue);
+ delete this.resetOriginal;
+ } else {
+ this.eachItem(function(c){
+ if(c.reset){
+ c.reset();
+ }
+ });
+ }
+ // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
+ // Important because reset is being called on both the group and the individual items.
+ (function() {
+ this.clearInvalid();
+ }).defer(50, this);
+ },
+
+ /**
+ * {@link Ext.form.Checkbox#setValue Set the value(s)} of an item or items
+ * in the group. Examples illustrating how this method may be called:
+ * <pre><code>
+// call with name and value
+myCheckboxGroup.setValue('cb-col-1', true);
+// call with an array of boolean values
+myCheckboxGroup.setValue([true, false, false]);
+// call with an object literal specifying item:value pairs
+myCheckboxGroup.setValue({
+ 'cb-col-2': false,
+ 'cb-col-3': true
+});
+// use comma separated string to set items with name to true (checked)
+myCheckboxGroup.setValue('cb-col-1,cb-col-3');
+ * </code></pre>
+ * See {@link Ext.form.Checkbox#setValue} for additional information.
+ * @param {Mixed} id The checkbox to check, or as described by example shown.
+ * @param {Boolean} value (optional) The value to set the item.
+ * @return {Ext.form.CheckboxGroup} this
+ */
+ setValue: function(){
+ if(this.rendered){
+ this.onSetValue.apply(this, arguments);
+ }else{
+ this.buffered = true;
+ this.value = arguments;
+ }
+ return this;
+ },
+
+ /**
+ * @private
+ * Sets the values of one or more of the items within the CheckboxGroup
+ * @param {String|Array|Object} id Can take multiple forms. Can be optionally:
+ * <ul>
+ * <li>An ID string to be used with a second argument</li>
+ * <li>An array of the form ['some', 'list', 'of', 'ids', 'to', 'mark', 'checked']</li>
+ * <li>An array in the form [true, true, false, true, false] etc, where each item relates to the check status of
+ * the checkbox at the same index</li>
+ * <li>An object containing ids of the checkboxes as keys and check values as properties</li>
+ * </ul>
+ * @param {String} value The value to set the field to if the first argument was a string
+ */
+ onSetValue: function(id, value){
+ if(arguments.length == 1){
+ if(Ext.isArray(id)){
+ Ext.each(id, function(val, idx){
+ if (Ext.isObject(val) && val.setValue){ // array of checkbox components to be checked
+ val.setValue(true);
+ if (this.resetOriginal === true) {
+ val.originalValue = val.getValue();
+ }
+ } else { // an array of boolean values
+ var item = this.items.itemAt(idx);
+ if(item){
+ item.setValue(val);
+ }
+ }
+ }, this);
+ }else if(Ext.isObject(id)){
+ // set of name/value pairs
+ for(var i in id){
+ var f = this.getBox(i);
+ if(f){
+ f.setValue(id[i]);
+ }
+ }
+ }else{
+ this.setValueForItem(id);
+ }
+ }else{
+ var f = this.getBox(id);
+ if(f){
+ f.setValue(value);
+ }
+ }
+ },
+
+ // private
+ beforeDestroy: function(){
+ Ext.destroy(this.panel);
+ Ext.form.CheckboxGroup.superclass.beforeDestroy.call(this);
+
+ },
+
+ setValueForItem : function(val){
+ val = String(val).split(',');
+ this.eachItem(function(item){
+ if(val.indexOf(item.inputValue)> -1){
+ item.setValue(true);
+ }
+ });
+ },
+
+ // private
+ getBox : function(id){
+ var box = null;
+ this.eachItem(function(f){
+ if(id == f || f.dataIndex == id || f.id == id || f.getName() == id){
+ box = f;
+ return false;
+ }
+ });
+ return box;
+ },
+
+ /**
+ * Gets an array of the selected {@link Ext.form.Checkbox} in the group.
+ * @return {Array} An array of the selected checkboxes.
+ */
+ getValue : function(){
+ var out = [];
+ this.eachItem(function(item){
+ if(item.checked){
+ out.push(item);
+ }
+ });
+ return out;
+ },
+
+ /**
+ * @private
+ * Convenience function which passes the given function to every item in the composite
+ * @param {Function} fn The function to call
+ * @param {Object} scope Optional scope object
+ */
+ eachItem: function(fn, scope) {
+ if(this.items && this.items.each){
+ this.items.each(fn, scope || this);
+ }
+ },
+
+ /**
+ * @cfg {String} name
+ * @hide
+ */
+
+ /**
+ * @method getRawValue
+ * @hide
+ */
+ getRawValue : Ext.emptyFn,
+
+ /**
+ * @method setRawValue
+ * @hide
+ */
+ setRawValue : Ext.emptyFn
+
+});
+
+Ext.reg('checkboxgroup', Ext.form.CheckboxGroup);
+/**
+ * @class Ext.form.CompositeField
+ * @extends Ext.form.Field
+ * Composite field allowing a number of form Fields to be rendered on the same row. The fields are rendered
+ * using an hbox layout internally, so all of the normal HBox layout config items are available. Example usage:
+ * <pre>
+{
+ xtype: 'compositefield',
+ labelWidth: 120
+ items: [
+ {
+ xtype : 'textfield',
+ fieldLabel: 'Title',
+ width : 20
+ },
+ {
+ xtype : 'textfield',
+ fieldLabel: 'First',
+ flex : 1
+ },
+ {
+ xtype : 'textfield',
+ fieldLabel: 'Last',
+ flex : 1
+ }
+ ]
+}
+ * </pre>
+ * In the example above the composite's fieldLabel will be set to 'Title, First, Last' as it groups the fieldLabels
+ * of each of its children. This can be overridden by setting a fieldLabel on the compositefield itself:
+ * <pre>
+{
+ xtype: 'compositefield',
+ fieldLabel: 'Custom label',
+ items: [...]
+}
+ * </pre>
+ * Any Ext.form.* component can be placed inside a composite field.
+ */
+Ext.form.CompositeField = Ext.extend(Ext.form.Field, {
+
+ /**
+ * @property defaultMargins
+ * @type String
+ * The margins to apply by default to each field in the composite
+ */
+ defaultMargins: '0 5 0 0',
+
+ /**
+ * @property skipLastItemMargin
+ * @type Boolean
+ * If true, the defaultMargins are not applied to the last item in the composite field set (defaults to true)
+ */
+ skipLastItemMargin: true,
+
+ /**
+ * @property isComposite
+ * @type Boolean
+ * Signifies that this is a Composite field
+ */
+ isComposite: true,
+
+ /**
+ * @property combineErrors
+ * @type Boolean
+ * True to combine errors from the individual fields into a single error message at the CompositeField level (defaults to true)
+ */
+ combineErrors: true,
+
+ //inherit docs
+ //Builds the composite field label
+ initComponent: function() {
+ var labels = [],
+ items = this.items,
+ item;
+
+ for (var i=0, j = items.length; i < j; i++) {
+ item = items[i];
+
+ labels.push(item.fieldLabel);
+
+ //apply any defaults
+ Ext.apply(item, this.defaults);
+
+ //apply default margins to each item except the last
+ if (!(i == j - 1 && this.skipLastItemMargin)) {
+ Ext.applyIf(item, {margins: this.defaultMargins});
+ }
+ }
+
+ this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
+
+ /**
+ * @property fieldErrors
+ * @type Ext.util.MixedCollection
+ * MixedCollection of current errors on the Composite's subfields. This is used internally to track when
+ * to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
+ * add, remove and replace events to update the error icon in the UI as errors are added or removed.
+ */
+ this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
+ return item.field;
+ });
+
+ this.fieldErrors.on({
+ scope : this,
+ add : this.updateInvalidMark,
+ remove : this.updateInvalidMark,
+ replace: this.updateInvalidMark
+ });
+
+ Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
+ },
+
+ /**
+ * @private
+ * Creates an internal container using hbox and renders the fields to it
+ */
+ onRender: function(ct, position) {
+ if (!this.el) {
+ /**
+ * @property innerCt
+ * @type Ext.Container
+ * A container configured with hbox layout which is responsible for laying out the subfields
+ */
+ var innerCt = this.innerCt = new Ext.Container({
+ layout : 'hbox',
+ renderTo: ct,
+ items : this.items,
+ cls : 'x-form-composite',
+ defaultMargins: '0 3 0 0'
+ });
+
+ this.el = innerCt.getEl();
+
+ var fields = innerCt.findBy(function(c) {
+ return c.isFormField;
+ }, this);
+
+ /**
+ * @property items
+ * @type Ext.util.MixedCollection
+ * Internal collection of all of the subfields in this Composite
+ */
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+
+ //if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
+ //methods of each subfield and show them at the Composite level instead
+ if (this.combineErrors) {
+ this.eachItem(function(field) {
+ Ext.apply(field, {
+ markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
+ clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
+ });
+ });
+ }
+
+ //set the label 'for' to the first item
+ var l = this.el.parent().parent().child('label', true);
+ if (l) {
+ l.setAttribute('for', this.items.items[0].id);
+ }
+ }
+
+ Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
+ },
+
+ /**
+ * Called if combineErrors is true and a subfield's markInvalid method is called.
+ * By default this just adds the subfield's error to the internal fieldErrors MixedCollection
+ * @param {Ext.form.Field} field The field that was marked invalid
+ * @param {String} message The error message
+ */
+ onFieldMarkInvalid: function(field, message) {
+ var name = field.getName(),
+ error = {field: name, error: message};
+
+ this.fieldErrors.replace(name, error);
+
+ field.el.addClass(field.invalidClass);
+ },
+
+ /**
+ * Called if combineErrors is true and a subfield's clearInvalid method is called.
+ * By default this just updates the internal fieldErrors MixedCollection.
+ * @param {Ext.form.Field} field The field that was marked invalid
+ */
+ onFieldClearInvalid: function(field) {
+ this.fieldErrors.removeKey(field.getName());
+
+ field.el.removeClass(field.invalidClass);
+ },
+
+ /**
+ * @private
+ * Called after a subfield is marked valid or invalid, this checks to see if any of the subfields are
+ * currently invalid. If any subfields are invalid it builds a combined error message marks the composite
+ * invalid, otherwise clearInvalid is called
+ */
+ updateInvalidMark: function() {
+ var ieStrict = Ext.isIE6 && Ext.isStrict;
+
+ if (this.fieldErrors.length == 0) {
+ this.clearInvalid();
+
+ //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
+ if (ieStrict) {
+ this.clearInvalid.defer(50, this);
+ }
+ } else {
+ var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
+
+ this.sortErrors();
+ this.markInvalid(message);
+
+ //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
+ if (ieStrict) {
+ this.markInvalid(message);
+ }
+ }
+ },
+
+ /**
+ * Performs validation checks on each subfield and returns false if any of them fail validation.
+ * @return {Boolean} False if any subfield failed validation
+ */
+ validateValue: function() {
+ var valid = true;
+
+ this.eachItem(function(field) {
+ if (!field.isValid()) valid = false;
+ });
+
+ return valid;
+ },
+
+ /**
+ * Takes an object containing error messages for contained fields, returning a combined error
+ * string (defaults to just placing each item on a new line). This can be overridden to provide
+ * custom combined error message handling.
+ * @param {Array} errors Array of errors in format: [{field: 'title', error: 'some error'}]
+ * @return {String} The combined error message
+ */
+ buildCombinedErrorMessage: function(errors) {
+ var combined = [],
+ error;
+
+ for (var i = 0, j = errors.length; i < j; i++) {
+ error = errors[i];
+
+ combined.push(String.format("{0}: {1}", error.field, error.error));
+ }
+
+ return combined.join("<br />");
+ },
+
+ /**
+ * Sorts the internal fieldErrors MixedCollection by the order in which the fields are defined.
+ * This is called before displaying errors to ensure that the errors are presented in the expected order.
+ * This function can be overridden to provide a custom sorting order if needed.
+ */
+ sortErrors: function() {
+ var fields = this.items;
+
+ this.fieldErrors.sort("ASC", function(a, b) {
+ var findByName = function(key) {
+ return function(field) {
+ return field.getName() == key;
+ };
+ };
+
+ var aIndex = fields.findIndexBy(findByName(a.field)),
+ bIndex = fields.findIndexBy(findByName(b.field));
+
+ return aIndex < bIndex ? -1 : 1;
+ });
+ },
+
+ /**
+ * Resets each field in the composite to their previous value
+ */
+ reset: function() {
+ this.eachItem(function(item) {
+ item.reset();
+ });
+
+ // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
+ // Important because reset is being called on both the group and the individual items.
+ (function() {
+ this.clearInvalid();
+ }).defer(50, this);
+ },
+
+ /**
+ * Calls clearInvalid on all child fields. This is a convenience function and should not often need to be called
+ * as fields usually take care of clearing themselves
+ */
+ clearInvalidChildren: function() {
+ this.eachItem(function(item) {
+ item.clearInvalid();
+ });
+ },
+
+ /**
+ * Builds a label string from an array of subfield labels.
+ * By default this just joins the labels together with a comma
+ * @param {Array} segments Array of each of the labels in the composite field's subfields
+ * @return {String} The built label
+ */
+ buildLabel: function(segments) {
+ return segments.join(", ");
+ },
+
+ /**
+ * Checks each field in the composite and returns true if any is dirty
+ * @return {Boolean} True if any field is dirty
+ */
+ isDirty: function(){
+ //override the behaviour to check sub items.
+ if (this.disabled || !this.rendered) {
+ return false;
+ }
+
+ var dirty = false;
+ this.eachItem(function(item){
+ if(item.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+ return dirty;
+ },
+
+ /**
+ * @private
+ * Convenience function which passes the given function to every item in the composite
+ * @param {Function} fn The function to call
+ * @param {Object} scope Optional scope object
+ */
+ eachItem: function(fn, scope) {
+ if(this.items && this.items.each){
+ this.items.each(fn, scope || this);
+ }
+ },
+
+ /**
+ * @private
+ * Passes the resize call through to the inner panel
+ */
+ onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
+ var innerCt = this.innerCt;
+
+ if (this.rendered && innerCt.rendered) {
+ innerCt.setSize(adjWidth, adjHeight);
+ }
+
+ Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
+ },
+
+ /**
+ * @private
+ * Forces the internal container to be laid out again
+ */
+ doLayout: function(shallow, force) {
+ if (this.rendered) {
+ var innerCt = this.innerCt;
+
+ innerCt.forceLayout = this.ownerCt.forceLayout;
+ innerCt.doLayout(shallow, force);
+ }
+ },
+
+ /**
+ * @private
+ */
+ beforeDestroy: function(){
+ Ext.destroy(this.innerCt);
+
+ Ext.form.CompositeField.superclass.beforeDestroy.call(this);
+ },
+
+ //override the behaviour to check sub items.
+ setReadOnly : function(readOnly) {
+ readOnly = readOnly || true;
+
+ if(this.rendered){
+ this.eachItem(function(item){
+ item.setReadOnly(readOnly);
+ });
+ }
+ this.readOnly = readOnly;
+ },
+
+ onShow : function() {
+ Ext.form.CompositeField.superclass.onShow.call(this);
+ this.doLayout();
+ },
+
+ //override the behaviour to check sub items.
+ onDisable : function(){
+ this.eachItem(function(item){
+ item.disable();
+ });
+ },
+
+ //override the behaviour to check sub items.
+ onEnable : function(){
+ this.eachItem(function(item){
+ item.enable();
+ });
+ }
+});
+
+Ext.reg('compositefield', Ext.form.CompositeField);
+/**
+ * @class Ext.form.Radio
+ * @extends Ext.form.Checkbox
+ * Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
+ * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
+ * @constructor
+ * Creates a new Radio
+ * @param {Object} config Configuration options
+ * @xtype radio
+ */
+Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
+ inputType: 'radio',
+
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ markInvalid : Ext.emptyFn,
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ clearInvalid : Ext.emptyFn,
+
+ /**
+ * If this radio is part of a group, it will return the selected value
+ * @return {String}
+ */
+ getGroupValue : function(){
+ var p = this.el.up('form') || Ext.getBody();
+ var c = p.child('input[name='+this.el.dom.name+']:checked', true);
+ return c ? c.value : null;
+ },
+
+ // private
+ onClick : function(){
+ if(this.el.dom.checked != this.checked){
+ var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
+ els.each(function(el){
+ if(el.dom.id == this.id){
+ this.setValue(true);
+ }else{
+ Ext.getCmp(el.dom.id).setValue(false);
+ }
+ }, this);
+ }
+ },
+
+ /**
+ * Sets either the checked/unchecked status of this Radio, or, if a string value
+ * is passed, checks a sibling Radio of the same name whose value is the value specified.
+ * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
+ * @return {Ext.form.Field} this
+ */
+ setValue : function(v){
+ if (typeof v == 'boolean') {
+ Ext.form.Radio.superclass.setValue.call(this, v);
+ } else if (this.rendered) {
+ var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
+ if(r){
+ Ext.getCmp(r.id).setValue(true);
+ }
+ }
+ return this;
+ },
+
+ // private
+ getCheckEl: function(){
+ if(this.inGroup){
+ return this.el.up('.x-form-radio-group')
+ }
+ return this.el.up('form') || Ext.getBody();
+ }
+});
+Ext.reg('radio', Ext.form.Radio);
+/**
+ * @class Ext.form.RadioGroup
+ * @extends Ext.form.CheckboxGroup
+ * A grouping container for {@link Ext.form.Radio} controls.
+ * @constructor
+ * Creates a new RadioGroup
+ * @param {Object} config Configuration options
+ * @xtype radiogroup
+ */
+Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
+ /**
+ * @cfg {Array} items An Array of {@link Ext.form.Radio Radio}s or Radio config objects
+ * to arrange in the group.
+ */
+ /**
+ * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
+ * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
+ * be used as the error text.
+ */
+ allowBlank : true,
+ /**
+ * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
+ * (defaults to 'You must select one item in this group')
+ */
+ blankText : 'You must select one item in this group',
+
+ // private
+ defaultType : 'radio',
+
+ // private
+ groupCls : 'x-form-radio-group',
+
+ /**
+ * @event change
+ * Fires when the state of a child radio changes.
+ * @param {Ext.form.RadioGroup} this
+ * @param {Ext.form.Radio} checked The checked radio
+ */
+
+ /**
+ * Gets the selected {@link Ext.form.Radio} in the group, if it exists.
+ * @return {Ext.form.Radio} The selected radio.
+ */
+ getValue : function(){
+ var out = null;
+ this.eachItem(function(item){
+ if(item.checked){
+ out = item;
+ return false;
+ }
+ });
+ return out;
+ },
+
+ /**
+ * Sets the checked radio in the group.
+ * @param {String/Ext.form.Radio} id The radio to check.
+ * @param {Boolean} value The value to set the radio.
+ * @return {Ext.form.RadioGroup} this
+ */
+ onSetValue : function(id, value){
+ if(arguments.length > 1){
+ var f = this.getBox(id);
+ if(f){
+ f.setValue(value);
+ if(f.checked){
+ this.eachItem(function(item){
+ if (item !== f){
+ item.setValue(false);
+ }
+ });
+ }
+ }
+ }else{
+ this.setValueForItem(id);
+ }
+ },
+
+ setValueForItem : function(val){
+ val = String(val).split(',')[0];
+ this.eachItem(function(item){
+ item.setValue(val == item.inputValue);
+ });
+ },
+
+ // private
+ fireChecked : function(){
+ if(!this.checkTask){
+ this.checkTask = new Ext.util.DelayedTask(this.bufferChecked, this);
+ }
+ this.checkTask.delay(10);
+ },
+
+ // private
+ bufferChecked : function(){
+ var out = null;
+ this.eachItem(function(item){
+ if(item.checked){
+ out = item;
+ return false;
+ }
+ });
+ this.fireEvent('change', this, out);
+ },
+
+ onDestroy : function(){
+ if(this.checkTask){
+ this.checkTask.cancel();
+ this.checkTask = null;
+ }
+ Ext.form.RadioGroup.superclass.onDestroy.call(this);
+ }
+
+});
+
+Ext.reg('radiogroup', Ext.form.RadioGroup);
+/**
+ * @class Ext.form.Hidden
+ * @extends Ext.form.Field
+ * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
+ * @constructor
+ * Create a new Hidden field.
+ * @param {Object} config Configuration options
+ * @xtype hidden
+ */
+Ext.form.Hidden = Ext.extend(Ext.form.Field, {
+ // private
+ inputType : 'hidden',
+
+ // private
+ onRender : function(){
+ Ext.form.Hidden.superclass.onRender.apply(this, arguments);
+ },
+
+ // private
+ initEvents : function(){
+ this.originalValue = this.getValue();
+ },
+
+ // These are all private overrides
+ setSize : Ext.emptyFn,
+ setWidth : Ext.emptyFn,
+ setHeight : Ext.emptyFn,
+ setPosition : Ext.emptyFn,
+ setPagePosition : Ext.emptyFn,
+ markInvalid : Ext.emptyFn,
+ clearInvalid : Ext.emptyFn
+});
+Ext.reg('hidden', Ext.form.Hidden);/**
+ * @class Ext.form.BasicForm
+ * @extends Ext.util.Observable
+ * <p>Encapsulates the DOM <form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
+ * input field management, validation, submission, and form loading services.</p>
+ * <p>By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
+ * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option.</p>
+ * <p><b><u>File Uploads</u></b></p>
+ * <p>{@link #fileUpload File uploads} are not performed using Ajax submission, that
+ * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
+ * manner with the DOM <tt><form></tt> element temporarily modified to have its
+ * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
+ * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
+ * "<" as "&lt;", "&" as "&amp;" etc.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
+ * and some server technologies (notably JEE) may require some custom processing in order to
+ * retrieve parameter names and parameter values from the packet content.</p>
+ * @constructor
+ * @param {Mixed} el The form element or its id
+ * @param {Object} config Configuration options
+ */
+Ext.form.BasicForm = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(el, config){
+ Ext.apply(this, config);
+ if(Ext.isString(this.paramOrder)){
+ this.paramOrder = this.paramOrder.split(/[\s,|]/);
+ }
+ /**
+ * A {@link Ext.util.MixedCollection MixedCollection} containing all the Ext.form.Fields in this form.
+ * @type MixedCollection
+ * @property items
+ */
+ this.items = new Ext.util.MixedCollection(false, function(o){
+ return o.getItemId();
+ });
+ this.addEvents(
+ /**
+ * @event beforeaction
+ * Fires before any action is performed. Return false to cancel the action.
+ * @param {Form} this
+ * @param {Action} action The {@link Ext.form.Action} to be performed
+ */
+ 'beforeaction',
+ /**
+ * @event actionfailed
+ * Fires when an action fails.
+ * @param {Form} this
+ * @param {Action} action The {@link Ext.form.Action} that failed
+ */
+ 'actionfailed',
+ /**
+ * @event actioncomplete
+ * Fires when an action is completed.
+ * @param {Form} this
+ * @param {Action} action The {@link Ext.form.Action} that completed
+ */
+ 'actioncomplete'
+ );
+
+ if(el){
+ this.initEl(el);
+ }
+ Ext.form.BasicForm.superclass.constructor.call(this);
+ },
+
+ /**
+ * @cfg {String} method
+ * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
+ */
+ /**
+ * @cfg {DataReader} reader
+ * An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to read
+ * data when executing 'load' actions. This is optional as there is built-in
+ * support for processing JSON. For additional information on using an XMLReader
+ * see the example provided in examples/form/xml-form.html.
+ */
+ /**
+ * @cfg {DataReader} errorReader
+ * <p>An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to
+ * read field error messages returned from 'submit' actions. This is optional
+ * as there is built-in support for processing JSON.</p>
+ * <p>The Records which provide messages for the invalid Fields must use the
+ * Field name (or id) as the Record ID, and must contain a field called 'msg'
+ * which contains the error message.</p>
+ * <p>The errorReader does not have to be a full-blown implementation of a
+ * DataReader. It simply needs to implement a <tt>read(xhr)</tt> function
+ * which returns an Array of Records in an object with the following
+ * structure:</p><pre><code>
+{
+ records: recordArray
+}
+</code></pre>
+ */
+ /**
+ * @cfg {String} url
+ * The URL to use for form actions if one isn't supplied in the
+ * <code>{@link #doAction doAction} options</code>.
+ */
+ /**
+ * @cfg {Boolean} fileUpload
+ * Set to true if this form is a file upload.
+ * <p>File uploads are not performed using normal 'Ajax' techniques, that is they are <b>not</b>
+ * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
+ * DOM <tt><form></tt> element temporarily modified to have its
+ * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
+ * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
+ * "<" as "&lt;", "&" as "&amp;" etc.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
+ * and some server technologies (notably JEE) may require some custom processing in order to
+ * retrieve parameter names and parameter values from the packet content.</p>
+ */
+ /**
+ * @cfg {Object} baseParams
+ * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
+ * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
+ */
+ /**
+ * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
+ */
+ timeout: 30,
+
+ /**
+ * @cfg {Object} api (Optional) If specified load and submit actions will be handled
+ * with {@link Ext.form.Action.DirectLoad} and {@link Ext.form.Action.DirectSubmit}.
+ * Methods which have been imported by Ext.Direct can be specified here to load and submit
+ * forms.
+ * Such as the following:<pre><code>
+api: {
+ load: App.ss.MyProfile.load,
+ submit: App.ss.MyProfile.submit
+}
+</code></pre>
+ * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
+ * to customize how the load method is invoked.
+ * Submit actions will always use a standard form submit. The formHandler configuration must
+ * be set on the associated server-side method which has been imported by Ext.Direct</p>
+ */
+
+ /**
+ * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
+ * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
+ * <code>load</code> configuration.</p>
+ * <br><p>Specify the params in the order in which they must be executed on the
+ * server-side as either (1) an Array of String values, or (2) a String of params
+ * delimited by either whitespace, comma, or pipe. For example,
+ * any of the following would be acceptable:</p><pre><code>
+paramOrder: ['param1','param2','param3']
+paramOrder: 'param1 param2 param3'
+paramOrder: 'param1,param2,param3'
+paramOrder: 'param1|param2|param'
+ </code></pre>
+ */
+ paramOrder: undefined,
+
+ /**
+ * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
+ * <code>load</code> configuration. Send parameters as a collection of named
+ * arguments (defaults to <tt>false</tt>). Providing a
+ * <tt>{@link #paramOrder}</tt> nullifies this configuration.
+ */
+ paramsAsHash: false,
+
+ /**
+ * @cfg {String} waitTitle
+ * The default title to show for the waiting message box (defaults to <tt>'Please Wait...'</tt>)
+ */
+ waitTitle: 'Please Wait...',
+
+ // private
+ activeAction : null,
+
+ /**
+ * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
+ * or {@link #setValues}() data instead of when the form was first created. Defaults to <tt>false</tt>.
+ */
+ trackResetOnLoad : false,
+
+ /**
+ * @cfg {Boolean} standardSubmit
+ * <p>If set to <tt>true</tt>, standard HTML form submits are used instead
+ * of XHR (Ajax) style form submissions. Defaults to <tt>false</tt>.</p>
+ * <br><p><b>Note:</b> When using <code>standardSubmit</code>, the
+ * <code>options</code> to <code>{@link #submit}</code> are ignored because
+ * Ext's Ajax infrastracture is bypassed. To pass extra parameters (e.g.
+ * <code>baseParams</code> and <code>params</code>), utilize hidden fields
+ * to submit extra data, for example:</p>
+ * <pre><code>
+new Ext.FormPanel({