/**
 * @class ST.future.Component
 * @extend ST.future.Element
 * This class is used to manage an Ext JS Component (`Ext.Component`) that will exist at
 * some point in the future. The inherited features of {@link ST.future.Element} all
 * operate on the component's primary element.
 *
 * The general mechanics of futures is covered in {@link ST.future.Element}.
 *
 * This class extends its base to provide additional `Ext.Component` specific action
 * and state methods.
 *
 * ### Note
 *
 * This class is not created directly by user code. Instead, it is created automatically
 * by {@link ST#component} or one of the more specific factory methods:
 *
 *  - `{@link ST#button}`
 *  - `{@link ST#checkBox}`
 *  - `{@link ST#comboBox}`
 *  - `{@link ST#dataView}`
 *  - `{@link ST#field}`
 *  - `{@link ST#grid}`
 *  - `{@link ST#panel}`
 *  - `{@link ST#picker}`
 *  - `{@link ST#textField}`
 *
 */
ST.future.define('Component', {
    extend: ST.future.Element,
    valueProperty: 'cmp',
 
    playables: {
        // TODO - focusentered 
        // TODO - focusleft 
 
        /**
         * @method destroyed
         * Waits for this component to be destroyed.
         *
         *      ST.component('@someCmp').
         *          destroyed().
         *          and(function (cmp) {
         *              // cmp is now destroyed
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Component} this
         * @chainable
         */
        destroyed: {
            remoteable: false,
            available: null,
            visibility: null,
 
            is: function () {
                var cmp = this.getComponent();
                return cmp ? (cmp.destroyed || cmp.isDestroyed) : true;
            },
 
            wait: 'destroy'
        },
 
        /**
         * @method disabled
         * Waits for this component to be disabled.
         *
         *      ST.component('@someCmp').
         *          disabled().
         *          and(function (cmp) {
         *              // cmp is now disabled
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Component} this
         * @chainable
         */
        disabled: {
            is: function () {
                var cmp = this.getComponent();
                return ST.isClassic ? cmp.disabled : cmp.getDisabled();
            },
            wait: ['disable', 'disabledchange']
        },
 
        /**
         * @method enabled
         * Waits for this component to be enabled.
         *
         *      ST.component('@someCmp').
         *          enabled().
         *          and(function (cmp) {
         *              // cmp is now enabled
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Component} this
         * @chainable
         */
        enabled: {
            is: function () {
                var cmp = this.getComponent();
                return ST.isClassic ? !cmp.disabled : !cmp.getDisabled();
            },
            wait: ['enable', 'disabledchange']
        },
 
        /**
         * @method rendered
         * Waits for this component to be rendered. This wait method only works when
         * the `ST.component` method is given an un-rendered Component instance. If a
         * locator string is used, the `ST.component` method will implicitly wait for
         * the Component's element to be present in the DOM (i.e., the component is in
         * a rendered state).
         *
         * Since this wait is normally handled by the `ST.component` method, this wait
         * method is seldom needed.
         *
         *      ST.component(comp).  // comp is an unrendered Component instance
         *          rendered().
         *          and(function () {
         *              // comp is now rendered
         *          });
         *
         * NOTE: This method is not supported for WebDriver-based tests.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Component} this
         * @chainable
         */
        rendered: {
            is: function () {
                var cmp = this.getComponent();
                return !ST.isClassic || cmp.rendered;
            },
            wait: 'afterrender'
        },
 
        /**
         * @method get
         * Retrieves the values of the specified list of properties from the future's 
         * underlying component or dom element. These values will then be available on the future itself 
         * (for example, to be used in expectations within an and() method).
         *
         * This method is particularly useful for WebDriver-based tests where direct access to Ext JS
         * components is not possible within the context of the executed spec.
         *
         *      ST.component('@futureCmp')
         *          .get('height')
         *          .and(function () {
         *              expect(this.future.data.height).toBe(200);
         *          })
         * 
         * The property names will be used to get information from the component or underlying dom element
         * according to the following priority.
         * 
         * For the property name 'foo':
         * 
         *      cmp.getFoo()
         *      cmp.foo
         *      cmp.el.dom.foo
         *      cmp.el.getStyle('foo')
         *
         * Investigating deep into the structure of a component is not supported. If you wish to interrogate
         * the structure of the component use either the {@link ST.future.Element#and} or {@link ST.future.Element#execute}
         * methods.
         *  
         * @param {String} properties A comma-delimited list of property values to retrieive
         * @return {ST.future.Component} this
         * @chainable
         */
        get: {
            params: 'names,timeout',
            fn: function () {
                var me = this,
                    future = me.future,
                    names = me.args.names.split(','),
                    cmp = me.getComponent(),
                    el = me.getElement(),
                    len = names.length, 
                    i, key, val, getter;
 
                future.data = future.data || {};
 
                if (cmp) {
                    for (i=0; i<len; i++) {
                        key = names[i];
                        val = cmp[key];
 
                        if (val === undefined) {
                            getter = cmp[ST.future.Element.createGetter(key)];
                            if (getter) {
                                val = getter.call(cmp);
                            } 
                        }
                        if (val === undefined && el.dom) {
                            val = el.dom[key];
                        }
                        if (val === undefined) {
                            val = el.getStyle(key);
                        }
                        if (val !== undefined) {
                            future.data[key] = val;
                        }
                    }
                }
            }
        },
        /**
         * @method disable
         * Schedules this component to be disabled.
         *
         *      ST.component('@someCmp').
         *          disable().
         *          and(function (cmp) {
         *              // cmp has now been disabled.
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Component} this
         * @chainable
         */
        disable: {
            params: 'timeout',
            fn: function () {
                this.getComponent().disable();
            }
        },
 
        /**
         * @method enable
         * Schedules this component to be enabled.
         *
         *      ST.component('@someCmp').
         *          enable().
         *          and(function (cmp) {
         *              // cmp has now been enabled.
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Component} this
         * @chainable
         */
        enable: {
            params: 'timeout',
            fn: function () {
                this.getComponent().enable();
            }
        },
 
        /**
         * @method fireEvent
         * 
         * Calls the fireEvent method on this future's component with the given
         * event name. Passing other arguments to the fireEvent method is not
         * supported at this time.
         * 
         * This was introduced to help in white-box style testing for WebDriver
         * scenarios. So that we could put special test-related event handlers
         * on components and fire them from a test.
         * 
         *      Ext.define('my.Text', {
         *          extend: 'Ext.form.field.Text',
         *          xtype: 'my-text',
         *          id: 'someCmp',
         *          fieldLable: 'My Text Field',
         *          listeners: {
         *              test: function () {
         *                  this.setValue('test-value-set');
         *              }
         *          }
         *      };
         * 
         *      ST.component('@someCmp')
         *          .fireEvent('test')
         *          .value('test-value-set');
         * 
         * @chainable
         * @return {ST.future.Component} this
         * @since 2.0.0
         */
        fireEvent: {
            params: 'eventname',
            fn: function () {
                this.future.cmp.fireEvent(this.args.eventname);
            }
        },
 
        /**
         * @private
         */
        asComponent: {
            addEvent: function (config, timeout) {
                var me = this;
 
                me.setRelated('item', config.item);
 
                me.locator = me.play(me._buildRec('asComponent', {
                    visible: null,
                    animation: false,
                    timeout: timeout
                }));
 
                return this;
            }
        }
    },
 
    constructor: function (config) {
        ST.future.Component.superclass.constructor.call(this, config);
    },
 
    /**
     * @method down
     * Returns a descendant `{@link ST.future.Element future element}` that corresponds to the selector.
     *
     *      ST.component('@someCmp').
     *          down('panel >> .link').
     *          and(function (element) {
     *              // element is now available
     *          });
     *
     * If the specified selector cannot be resolved, the request will timeout.
     * @param {String} selector The DOM or Composite query selector to use to search for the element
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Element}
     * @chainable
     */
    down: function (selector, timeout) {
        return this._createRelatedFuture('ST.future.Element', 'down', selector, timeout);
    },
 
    /**
     * @method up
     * Returns an ancestor `{@link ST.future.Element future element}` that corresponds to the selector.
     *
     *      ST.component('@someCmp').
     *          up('.wrapper').
     *          and(function (element) {
     *              // element is now available
     *          });
     *
     * If the specified selector cannot be resolved, the request will timeout.
     * @param {String} selector The DOM or Composite query selector to use to search for the element
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Element}
     * @chainable
     */
    up: function (selector, timeout) {
        return this._createRelatedFuture('ST.future.Element', 'up', selector, timeout);
    },
 
    /**
     * @method child
     * Returns a direct child `{@link ST.future.Element future element}` that corresponds to the selector.
     *
     *      ST.component('@someCmp').
     *          child('.x-button').
     *          and(function (element) {
     *              // element is now available
     *          });
     *
     * If the specified selector cannot be resolved, the request will timeout.
     * @param {String} selector The DOM or Composite query selector to use to search for the element
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Element}
     * @chainable
     */
    child: function (selector, timeout) {
        return this._createRelatedFuture('ST.future.Element', 'child', selector, timeout);
    },
 
    /**
     * @method gotoComponent
     * Returns a `{@ST.future.Component}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          goToComponent('container').
     *          and(function (container) {
     *              // container is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Component}
     * @chainable
     */
    gotoComponent: function (selector, direction, timeout) {
        return this._goto('ST.future.Component', direction, selector, timeout);
    },
 
    /**
     * @method gotoButton
     * Returns a `{@ST.future.Button}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoButton('button').
     *          and(function (button) {
     *              // button is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Button}
     * @chainable
     */
    gotoButton: function (selector, direction, timeout) {
        return this._goto('ST.future.Button', direction, selector, timeout);
    },
 
    /**
     * @method gotoField
     * Returns a `{@ST.future.Field}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoField('field').
     *          and(function (field) {
     *              // field is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Field}
     * @chainable
     */
    gotoField: function (selector, direction, timeout) {
        return this._goto('ST.future.Field', direction, selector, timeout);
    },
 
    /**
     * @method gotoCheckBox
     * Returns a `{@ST.future.CheckBox}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoCheckBox('checkboxfield').
     *          and(function (checkbox) {
     *              // checkbox is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.CheckBox}
     * @chainable
     */
    gotoCheckBox: function (selector, direction, timeout) {
        return this._goto('ST.future.CheckBox', direction, selector, timeout);
    },
 
    /**
     * @method gotoTextField
     * Returns a `{@ST.future.TextField}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoTextField('textfield').
     *          and(function (textfield) {
     *              // textfield is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.TextField}
     * @chainable
     */
    gotoTextField: function (selector, direction, timeout) {
        return this._goto('ST.future.TextField', direction, selector, timeout);
    },
 
    /**
     * @method gotoPicker
     * Returns a `{@ST.future.Picker}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoPicker('pickerfield').
     *          and(function (picker) {
     *              // picker is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Picker}
     * @chainable
     */
    gotoPicker: function (selector, direction, timeout) {
        return this._goto('ST.future.Picker', direction, selector, timeout);
    },
 
    /**
     * @method gotoComboBox
     * Returns a `{@ST.future.ComboBox}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoComboBox('combobox').
     *          and(function (combobox) {
     *              // combobox is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.ComboBox}
     * @chainable
     */
    gotoComboBox: function (selector, direction, timeout) {
        return this._goto('ST.future.ComboBox', direction, selector, timeout);
    },
 
    /**
     * @method gotoSelect
     * Returns a `{@ST.future.Select}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoSelect('selectfield').
     *          and(function (select) {
     *              // select is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Select}
     * @chainable
     */
    gotoSelect: function (selector, direction, timeout) {
        return this._goto('ST.future.Select', direction, selector, timeout);
    },
 
    /**
     * @method gotoPanel
     * Returns a `{@ST.future.Panel}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoPanel('panel').
     *          and(function (panel) {
     *              // panel is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Panel}
     * @chainable
     */
    gotoPanel: function (selector, direction, timeout) {
        return this._goto('ST.future.Panel', direction, selector, timeout);
    },
 
    /**
     * @method gotoDataView
     * Returns a `{@ST.future.Panel}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoDataView('dataview').
     *          and(function (dataview) {
     *              // dataview is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.DataView}
     * @chainable
     */
    gotoDataView: function (selector, direction, timeout) {
        return this._goto('ST.future.DataView', direction, selector, timeout);
    },
 
    /**
     * @method gotoGrid
     * Returns a `{@ST.future.Panel}` future component that is hierarchically-related to the current component future
     *
     *      ST.component('@someCmp').
     *          gotoGrid('grid').
     *          and(function (grid) {
     *              // grid is now available
     *          });
     *
     * @param {String} selector The Component Query selector.
     * @param {"down"/"up"/"child"} [direction="down"] The direction of relationship.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @return {ST.future.Grid}
     * @chainable
     */
    gotoGrid: function (selector, direction, timeout) {
        return this._goto('ST.future.Grid', direction, selector, timeout);
    },
 
    _goto: function (maker, direction, selector, timeout) {
        return this._createRelatedFuture(maker, direction || 'down', selector, timeout);
    },
 
    // TODO - lookup 
 
    /**
     * Returns the owning `ST.future.Item`. This method can be called at any time
     * to "return" to the owning future. For example:
     *
     *      ST.dataView('@someDataView').
     *          item(42).            // get a future item (ST.future.Item)
     *              asButton().      // get item as a button (ST.future.Button)
     *                  press().     // operates on the ST.future.Button
     *              asItem();        // now back to the item
     *
     * This method is *only* applicable in conjuction with Ext JS Modern Toolkit (or Sencha Touch) when
     * using an Ext.dataview.DataView that is configured with `useComponents:true`.
     *
     * @return {ST.future.Item}
     */
    asItem: function () {
        var item = this.getRelated('item');
        var chainItem = {
            locator: '$$SKIP$$',
            futureClsName: 'ST.future.Item',
            type: 'element'
        };
        // adding an extra locatorChain item since one won't get added by default 
        item.locator.locatorChain.push(chainItem);
        // replay locator to ensure context is updated appropriately 
        this.play([item.locator]);
 
        return item;
    },
 
    _attach: function () {
        this.cmp = this.el.getComponent();
    },
 
    _getFocusEl: function() {
        var cmp = this.cmp,
            el, fld;
 
        // if using classic toolkit, we can use getFocusEl() 
        if (ST.isClassical()) {
            el = cmp.getFocusEl();
        }
        // if not classic and the type is a textfield, we can retrieve the input from the component 
        else if (cmp.isXType('textfield')) {
            if (cmp.getComponent) {
                fld = cmp.getComponent();
            } else {
                // in 6.5, Ext.field.Text no longer extends Ext.field.Decorator, so getComponent() doesn't exist 
                // however, the "field" component is just the component itself, so easy enough 
                fld = cmp;
            }
            
            el = fld.input || fld.inputElement; // 6.2+ changed input to inputElement 
        }
        // otherwise, just fallback to the element; this will accomodate Sencha Touch, and is the default for 
        // what getFocusEl() returns in the modern toolkit 
        else {
            el = cmp.el || cmp.element;
        }
 
        return el;
    }
});
 
/**
 * Returns a {@link ST.future.Component future component} used to queue operations for
 * when that component becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the component.
 * @return {ST.future.Component}
 * @method component
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// Button 
 
/**
 * This class provides methods specific to Ext JS Buttons (`Ext.button.Button`).
 *
 * @class ST.future.Button
 * @extend ST.future.Component
 */
ST.future.define('Button', {
    extend: ST.future.Component,
 
    //TODO menu, split 
 
    playables: {
        /**
         * @method pressed
         * Waits for this button to be pressed.
         * This only applies to Ext JS / Classic and Modern toolkit toggle buttons (as of version 6.0.2) and not in Sencha Touch.
         *
         *      ST.button('@someButton').
         *          pressed().
         *          and(function (button) {
         *              // button is now pressed
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        pressed: {
            is: function () {
                var cmp = this.getComponent();
                return ST.isClassic ? cmp.pressed : cmp.getPressed();
            },
            wait: ['toggle', 'pressedchange']
        },
 
        /**
         * @method unpressed
         * Waits for this button to be unpressed.
         * This only applies to Ext JS / Classic and Modern toolkit toggle buttons (as of version 6.0.2) and not in Sencha Touch.
         *
         *      ST.button('@someButton').
         *          unpressed().
         *          and(function (button) {
         *              // button is now unpressed
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        unpressed: {
            is: function () {
                var cmp = this.getComponent();
                return ST.isClassic ? !cmp.pressed : !cmp.getPressed();
            },
            wait: ['toggle', 'pressedchange']
        },
 
        /**
         * @method expanded
         * Waits for this button's menu to be shown.
         * This only applies to Ext JS / Classic toolkit buttons and not in the Modern toolkit or Sencha Touch.
         *
         *      ST.button('@someButton').
         *          expanded().
         *          and(function (button) {
         *              // button's menu is now visible
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        expanded: {
            is: function () {
                return this.getComponent().hasVisibleMenu();
            },
            wait: ['menushow']
        },
 
        /**
         * @method collapsed
         * Waits for this button's menu to be hidden.
         * This only applies to Ext JS / Classic toolkit buttons and not in the Modern toolkit or Sencha Touch.
         *
         *      ST.button('@someButton').
         *          collapsed().
         *          and(function (button) {
         *              // button's menu is now hidden
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        collapsed: {
            is: function () {
                return !this.getComponent().hasVisibleMenu();
            },
            wait: ['menuhide']
        },
 
        /**
         * @method press
         * Schedules this button to be pressed.
         * This only applies to Ext JS / Classic and Modern toolkit toggle buttons (as of version 6.0.2) and not in Sencha Touch.
         *
         *      ST.button('@someButton').
         *          press().
         *          and(function (button) {
         *              // button has now been pressed.
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        press: {
            params: 'timeout',
            fn: function () {
                this.getComponent().setPressed(true);
            }
        },
 
        /**
         * @method unpress
         * Schedules this button to be unpressed.
         * This only applies to Ext JS / Classic and Modern toolkit toggle buttons (as of version 6.0.2) and not in Sencha Touch.
         *
         *      ST.button('@someButton').
         *          unpress().
         *          and(function (button) {
         *              // button has now been unpressed.
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        unpress: {
            params: 'timeout',
            fn: function () {
                this.getComponent().setPressed(false);
            }
        },
 
        /**
         * @method expand
         * Schedules this button's menu to be shown.
         * This only applies to Ext JS / Classic toolkit buttons and not in the Modern toolkit or Sencha Touch.
         *
         *      ST.button('@someButton').
         *          expand().
         *          and(function (button) {
         *              // button's menu is shown
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        expand: {
            params: 'timeout',
            fn: function () {
                this.getComponent().showMenu();
            }
        },
 
        /**
         * @method collapse
         * Schedules this button's menu to be hidden.
         * This only applies to Ext JS / Classic toolkit buttons and not in the Modern toolkit or Sencha Touch.
         *
         *      ST.button('@someButton').
         *          collapse().
         *          and(function (button) {
         *              // button's menu is hidden
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Button} this
         * @chainable
         */
        collapse: {
            params: 'timeout',
            fn: function () {
                this.getComponent().hideMenu();
            }
        }
    }
});
 
/**
 * Returns a {@link ST.future.Button future button} used to queue operations for
 * when that button becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the button.
 * @return {ST.future.Button}
 * @method button
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// Field 
 
/**
 * This class provides methods specific to Ext JS Field (`Ext.form.field.Base`, `Ext.field.Field`). This
 * class can be used to wait on component states like so:
 *
 *      ST.field('@someField').
 *          value(42).
 *          and(function (field) {
 *              // field has received the value 42
 *          });
 *
 * It can also be used to manipulate the component state:
 *
 *      ST.field('@someField').
 *          setValue(42).
 *          and(function (field) {
 *              // field has now been set to the value 42
 *          });
 *
 * @class ST.future.Field
 * @extend ST.future.Component
 */
ST.future.define('Field', {
    extend: ST.future.Component,
 
    playables: {
        /**
         * @method value
         * Waits for this Field to have the specified value.
         *
         *      ST.field('@someField').
         *          value('Something').
         *          and(function (field) {
         *              // textField has received the value "Something"
         *          });
         *
         * @param {String} value The value for which to wait.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Field} this
         * @chainable
         */
        value: {
            params: 'value',
            is: function () {
                var v = this.getComponent().getValue();
                return v === this.args.value;
            },
            wait: 'change'
        },
 
        /**
         * @method valueEmpty
         * Waits for this Field to have an empty value.
         *
         *      ST.field('@someField').
         *          valueEmpty().
         *          and(function (field) {
         *              // field value is now empty
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Field} this
         * @chainable
         */
        valueEmpty: {
            is: function () {
                var v = this.getComponent().getValue();
                return !(|| v === 0);
            },
            wait: 'change'
        },
 
        /**
         * @method valueNotEmpty
         * Waits for this Field to have a non-empty value.
         *
         *      ST.field('@someField').
         *          valueNotEmpty().
         *          and(function (field) {
         *              // field value is now non-empty
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Field} this
         * @chainable
         */
        valueNotEmpty: {
            is: function () {
                var v = this.getComponent().getValue();
                return (|| v === 0);
            },
            wait: 'change'
        },
 
        /**
         * @method setValue
         * @chainable
         * Schedules a `setValue` call on the underlying Field.
         *
         *      ST.field('@someField').
         *          setValue('Something').
         *          and(function (field) {
         *              // field value is now "Something"
         *          });
         *
         * @param {String} value The new value for the text field.
         * @param {Number/Object} [timeout] The maximum time (in milliseconds) to wait.
         * @returns {ST.future.Field}
         */
        setValue: {
            addEvent: function () {
                var me = this,
                    rec = me._buildRec('setValue', arguments, 'value,timeout', {});
 
                me.play([rec]);
 
                return me;
            },
            fn: function () {
                var cmp = this.getComponent(),
                    value = this.args.value;
 
                cmp.setValue(value);
            }
        }
    }
});
 
/**
 * Returns a {@link ST.future.Field future field} used to queue operations for
 * when that component becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the component.
 * @return {ST.future.Field}
 * @method field
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// Checkbox 
 
/**
 * This class provides methods specific to Ext JS Checkbox (`Ext.form.field.Checkbox` or `Ext.field.Checkbox`).
 * In addition to `ST.future.Field` features, this class adds value `checked` and
 * `unchecked` testing. For example:
 *
 *      ST.checkBox('@someCheckBox').
 *          checked().
 *          and(function (checkbox) {
 *              // now checked
 *          }).
 *          unchecked().
 *          and(function (checkbox) {
 *              // not unchecked
 *          });
 *
 * @class ST.future.CheckBox
 * @extend ST.future.Field
 */
ST.future.define('CheckBox', {
    extend: ST.future.Field,
    playables: {
        /**
         * @method value
         * Waits for this CheckBox to have the specified value.
         *
         *      ST.checkBox('@someCheckBox').
         *          value(true);
         *
         * NOTE: The `value` of the underlying checkbox component may differ from the `checked` state,
         * depending on the Ext JS toolkit being used (or Sencha Touch), which may produce unexpected results
         * when using `value()` on the CheckBox future.
         *
         * It is therefore recommended to use `checked()` and/or `unchecked()` when interacting with
         * the checked state of the underlying checkbox component.
         *
         * @param {String} value The value for which to wait.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.CheckBox} this
         * @chainable
         */
 
        /**
         * @method valueEmpty
         * Waits for this CheckBox to have an empty value.
         *
         *      ST.checkBox('@someCheckBox').
         *          valueEmpty().
         *          and(function (checkBox) {
         *              // checkbox value is now empty
         *          });
         *
         * NOTE: The `value` of the underlying checkbox component may differ from the `checked` state,
         * depending on the Ext JS toolkit being used (or Sencha Touch), which may produce unexpected results
         * when using `valueEmpty()` on the CheckBox future.
         *
         * It is therefore recommended to use `checked()` and/or `unchecked()` when interacting with
         * the checked state of the underlying checkbox component.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.CheckBox} this
         * @chainable
         */
 
        /**
         * @method valueNotEmpty
         * Waits for this CheckBox to have a non-empty value.
         *
         *      ST.checkBox('@someCheckBox').
         *          valueNotEmpty().
         *          and(function (checkBox) {
         *              // checkbox value is now non-empty
         *          });
         *
         *
         * NOTE: The `value` of the underlying checkbox component may differ from the `checked` state,
         * depending on the Ext JS toolkit being used (or Sencha Touch), which may produce unexpected results
         * when using `valueNotEmpty()` on the CheckBox future.
         *
         * It is therefore recommended to use `checked()` and/or `unchecked()` when interacting with
         * the checked state of the underlying checkbox component.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.CheckBox} this
         * @chainable
         */
 
        /**
         * @method checked
         * Waits for the underlying CheckBox to be `checked`.
         *
         *      ST.checkBox('@someCheckBox').
         *          checked().
         *          and(function (checkBox) {
         *              // checkBox is in a checked state
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.CheckBox} this
         * @chainable
         */
        checked: {
            is: function () {
                var cmp = this.getComponent();
                return ST.isClassic ? cmp.checked : cmp.getChecked();
            },
            wait: 'change'
        },
 
        /**
         * @method unchecked
         * @chainable
         * Waits for the underlying CheckBox to be `unchecked`.
         *
         *      ST.checkBox('@someCheckBox').
         *          unchecked().
         *          and(function (checkBox) {
         *              // checkBox is in an unchecked state
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.CheckBox} this
         */
        unchecked: {
            is: function () {
                var cmp = this.getComponent();
                return ST.isClassic ? !cmp.checked : !cmp.getChecked();
            },
            wait: 'change'
        },
 
        /**
         * @method setValue
         * @chainable
         * Schedules a `setValue` call on the underlying CheckBox.
         *
         *      ST.checkBox('@someField').
         *          setValue(true);
         *
         * NOTE: The `value` of the underlying checkbox component may differ from the `checked` state,
         * depending on the Ext JS toolkit being used (or Sencha Touch), which may produce unexpected results
         * when using `setValue()` on the CheckBox future.
         *
         * It is therefore recommended to use `check()`, `uncheck()`, and `setChecked()`
         * when interacting with the checked state of the underlying checkbox component.
         *
         * @param {String} value The new value for the checkbox field.
         * @param {Number/Object} [timeout] The maximum time (in milliseconds) to wait.
         * @returns {ST.future.CheckBox}
         */
 
        /**
         * @method check
         * @chainable
         * Schedules the checkbox to be checked.
         *
         *      ST.checkBox('@someCheckBox').
         *          check().
         *          and(function (checkBox) {
         *              // checkBox is now checked
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @returns {ST.future.CheckBox}
         */
        check: {
            params: 'timeout',
            fn: function () {
                ST.future.CheckBox._setChecked(this.getComponent(), true);
            }
        },
 
        /**
         * @method uncheck
         * @chainable
         * Schedules the checkbox to be unchecked.
         *
         *      ST.checkBox('@someCheckBox').
         *          uncheck().
         *          and(function (checkBox) {
         *              // checkBox is now unchecked
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @returns {ST.future.CheckBox}
         */
        uncheck: {
            params: 'timeout',
            fn: function () {
                ST.future.CheckBox._setChecked(this.getComponent(), false);
            }
        }
    },
 
    statics: {
        _setChecked: function (cmp, checked) {
            if (ST.isClassic) {
                // classic does not have setChecked, so we need to use setValue 
                cmp.setValue(checked);
            } else {
                cmp.setChecked(checked);
            }
        }
    }
});
 
/**
 * Returns a {@link ST.future.CheckBox future checkBox} used to queue operations for
 * when that component becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the component.
 * @return {ST.future.CheckBox}
 * @method checkBox
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// TextField 
 
/**
 * This class provides methods specific to Ext JS TextField (`Ext.form.field.Text` or `Ext.field.Text`).
 * In addition to `ST.future.Field` features, this class adds value similarity
 * checking. For example:
 *
 *      ST.textField('@someTextField').
 *          valueLike('world').
 *          and(function (textField) {
 *              // the value now contains the text "world"
 *          }).
 *          valueNotLike(/^hello/i).
 *          and(function (textField) {
 *              // the value no longer starts with "hello" (ignoring case)
 *          });
 *
 * @class ST.future.TextField
 * @extend ST.future.Field
 */
ST.future.define('TextField', {
    extend: ST.future.Field,
 
    playables: {
        /**
         * @method valueLike
         * Waits for this TextField to have a value like the given `pattern`.
         *
         *      ST.textField('@someTextField').
         *          valueLike(/bar$/i).
         *          and(function (textField) {
         *              // textField value now ends with "bar" (ignoring case)
         *          });
         *
         * @param {RegExp/String} pattern The pattern to match. If this is a String,
         * it is first promoted to a `RegExp` by called `new RegExp(pattern)`.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.TextField} this
         * @chainable
         */
        valueLike: {
            addEvent: function () {
                var me = this,
                    rec = me._buildRec('valueLike', arguments, 'pattern,timeout', {
                        waitingFor: 'value',
                        waitingState: 'like '
                    });
 
                rec.waitingState += rec.args.pattern.source || rec.args.pattern.toString();
 
                me.play([rec]);
 
                return me;
            },
            is: function () {
                var cmp = this.getComponent(),
                    v = cmp.getValue(),
                    pattern = ST.decodeRegex(this.args.pattern),
                    re = (typeof pattern === 'string') ? new RegExp(pattern) : pattern;
 
                return re.test(v);
            },
            wait: 'change'
        },
 
        /**
         * @method valueNotLike
         * Waits for this TextField to have a value that does not match the given `pattern`.
         *
         *      ST.textField('@someTextField').
         *          valueNotLike(/bar$/i).
         *          and(function (textField) {
         *              // textField value does not end with "bar" (ignoring case)
         *          });
         *
         * @param {RegExp/String} pattern The pattern to match. If this is a String,
         * it is first promoted to a `RegExp` by called `new RegExp(pattern)`.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.TextField} this
         * @chainable
         */
        valueNotLike: {
            addEvent: function () {
                var me = this,
                    rec = me._buildRec('valueNotLike', arguments, 'pattern,timeout', {
                        waitingFor: 'value',
                        waitingState: 'not like '
                    });
 
                rec.waitingState += rec.args.pattern.source || rec.args.pattern.toString();
 
                me.play([rec]);
 
                return me;
            },
            is: function () {
                var cmp = this.getComponent(),
                    v = cmp.getValue(),
                    pattern = ST.decodeRegex(this.args.pattern),
                    re = (typeof pattern === 'string') ? new RegExp(pattern) : pattern;
 
                return !re.test(v);
            },
            wait: 'change'
        }
    }
});
 
/**
 * Returns a {@link ST.future.TextField future textfield} used to queue operations for
 * when that component becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the component.
 * @return {ST.future.TextField}
 * @method textField
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// Picker 
 
/**
 * This class provides methods specific to Ext JS Pickers (`Ext.form.field.Picker`). This only applies to Ext JS / Classic
 * and not in the Modern toolkit (as of version 6.0.1) or Sencha Touch.
 *
 * @class ST.future.Picker
 * @extend ST.future.TextField
 * @private
 */
ST.future.define('Picker', {
    extend: ST.future.TextField,
 
    playables: {
        /**
         * @method collapsed
         * Waits for this picker to be collapsed.
         *
         *      ST.picker('@somePicker').
         *          collapsed().
         *          and(function (picker) {
         *              // picker is now collapsed
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Picker} this
         * @chainable
         */
        collapsed: {
            is: function () {
                return !this.getComponent().isExpanded;
            },
            wait: 'collapse'
        },
 
        /**
         * @method expanded
         * Waits for this picker to be expanded.
         *
         *      ST.picker('@somePicker').
         *          expanded().
         *          and(function (picker) {
         *              // picker is now expanded
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Picker} this
         * @chainable
         */
        expanded: {
            is: function () {
                return this.getComponent().isExpanded;
            },
            wait: 'expand'
        },
 
        /**
         * @method collapse
         * Schedules this picker to collapse. All arguments passed to this method will be
         * forwarded to the `collapse` method of the picker at the appropriate time so
         * consult the documentation for the actual framework and version you are using
         * for parameter details.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Picker} this
         * @chainable
         */
        collapse: {
            params: 'timeout',
            fn: function () {
                this.getComponent().collapse();
            }
        },
 
        /**
         * @method expand
         * Schedules this picker to expand. All arguments passed to this method will be
         * forwarded to the `expand` method of the picker at the appropriate time so
         * consult the documentation for the actual framework and version you are using
         * for parameter details.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Picker} this
         * @chainable
         */
        expand: {
            params: 'timeout',
            fn: function () {
                this.getComponent().expand();
            }
        }
    }
});
 
/**
 * Returns a {@link ST.future.Picker future picker} used to queue operations for
 * when that `Ext.form.field.Picker` becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the picker.
 * @return {ST.future.Picker}
 * @method picker
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// ComboBox 
 
/**
 * This class provides methods specific to Ext JS ComboBox (`Ext.form.field.ComboBox`).
 * This is only available in Ext JS / Classic, not in the Modern toolkit (as of version 6.0.1) or Sencha Touch.
 *
 * @class ST.future.ComboBox
 * @extend ST.future.Picker
 */
ST.future.define('ComboBox', {
    extend: ST.future.Picker,
 
    playables: {
        // TODO - queried 
        // TODO - loaded 
        // TODO - selected 
    }
});
 
/**
 * Returns a {@link ST.future.ComboBox future ComboBox} used to queue operations for
 * when that `Ext.form.field.ComboBox` becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the combobox.
 * @return {ST.future.ComboBox}
 * @method comboBox
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// Select 
 
/**
 * This class provides methods specific to Ext JS Select fields (`Ext.field.Select`).
 * This only applies to Ext JS / Modern and Sencha Touch, and is not available in the Classic toolkit.
 *
 * @class ST.future.Select
 * @extend ST.future.TextField
 */
ST.future.define('Select', {
    extend: ST.future.TextField,
 
    playables: {
        /**
         * @method collapsed
         * Waits for this select field to be collapsed.
         *
         *      ST.select('@someSelect').
         *          collapsed().
         *          and(function (select) {
         *              // select field is now collapsed
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Select} this
         * @chainable
         */
        collapsed: {
            is: function () {
                var picker = ST.future.Select.getPicker(this.getComponent());
                return picker.isHidden();
            },
            wait: function (done) {
                var picker = ST.future.Select.getPicker(this.getComponent()),
                    listener = {
                        hide: done,
                        single: true
                    };
 
                picker.on(listener);
 
                return function () {
                    picker.un(listener)
                }
            }
        },
 
        /**
         * @method expanded
         * Waits for this select field to be expanded.
         *
         *      ST.select('@someSelect').
         *          expanded().
         *          and(function (select) {
         *              // select field is now expanded
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Select} this
         * @chainable
         */
        expanded: {
            is: function () {
                var picker = ST.future.Select.getPicker(this.getComponent())
                return !picker.isHidden() && picker.isPainted();
            },
            wait: function (done) {
                var picker = ST.future.Select.getPicker(this.getComponent()),
                    listener = {
                        show: done,
                        single: true
                    };
 
                picker.on(listener);
 
                return function () {
                    picker.un(listener)
                }
            }
        },
 
        /**
         * @method collapse
         * Schedules this select field to collapse. All arguments passed to this method will be
         * forwarded to the `collapse` method of the select field at the appropriate time so
         * consult the documentation for the actual framework and version you are using
         * for parameter details.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Select} this
         * @chainable
         */
        collapse: {
            params: 'timeout',
            fn: function (done) {
                var cmp = this.getComponent(),
                    picker = ST.future.Select.getPicker(cmp),
                    needsCallback = cmp.getUsePicker();
 
                if (needsCallback) {
                    picker.on('hide', function () {
                        done();
                    }, {
                        single: true
                    });
                    picker.hide();
                } else {
                    picker.hide();
                    done();
                }
            }
        },
 
        /**
         * @method expand
         * Schedules this select field to expand. All arguments passed to this method will be
         * forwarded to the `expand` method of the select field at the appropriate time so
         * consult the documentation for the actual framework and version you are using
         * for parameter details.
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Select} this
         * @chainable
         */
        expand: {
            params: 'timeout',
            fn: function (done) {
                var cmp = this.getComponent(),
                    picker = ST.future.Select.getPicker(cmp),
                    needsCallback = cmp.getUsePicker();
 
                if (needsCallback) {
                    picker.on('show', function () {
                        done();
                    }, {
                        single: true
                    });
                    cmp.showPicker();
                } else {
                    cmp.showPicker();
                    done();
                }
            }
        }
    },
 
    statics: {
        /**
         * @private
         */
        getPicker: function (component) {
            return component.getUsePicker() ? component.getPhonePicker() : component.getTabletPicker();
        }
    }
});
 
/**
 * Returns a {@link ST.future.Select future select field} used to queue operations for
 * when that `Ext.field.Select` becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the select field.
 * @return {ST.future.Select}
 * @method select
 * @member ST
 */
 
//----------------------------------------------------------------------------- 
// Panel 
 
/**
 * This class provides methods specific to Ext JS Panels (`Ext.panel.Panel` or
 * `Ext.Panel`).
 *
 * @class ST.future.Panel
 * @extend ST.future.Component
 */
ST.future.define('Panel', {
    extend: ST.future.Component,
 
    playables: {
        /**
         * @method collapsed
         * Waits for this panel to be collapsed. This only applies to Ext JS / Classic
         * panels and not in the Modern toolkit (as of version 6.0.1) or Sencha Touch.
         *
         *      ST.panel('@somePanel').
         *          collapsed().
         *          and(function (panel) {
         *              // panel is now collapsed
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Panel} this
         * @chainable
         */
        collapsed: {
            is: function () {
                return this.getComponent().collapsed;
            },
            wait: 'collapse'
        },
 
        /**
         * @method expanded
         * Waits for this panel to be expanded. This only applies to Ext JS / Classic
         * panels and not in the Modern toolkit (as of version 6.0.1) or Sencha Touch.
         *
         *      ST.panel('@somePanel').
         *          expanded().
         *          and(function (panel) {
         *              // panel is now expanded
         *          });
         *
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Panel} this
         * @chainable
         */
        expanded: {
            is: function () {
                return !this.getComponent().collapsed;
            },
            wait: 'expand'
        },
 
        /**
         * @method expand
         * Schedules this panel to expand. All arguments passed to this method will be
         * forwarded to the `expand` method of the panel at the appropriate time so
         * consult the documentation for the actual framework and version you are using
         * for parameter details.
         *
         * @param {Boolean} [animate] True to animate the transition, else false
         * (defaults to the value of the `animCollapse` panel config).  May
         * also be specified as the animation duration in milliseconds.
         * @return {ST.future.Panel} this
         * @chainable
         */
        expand: {
            addEvent: function () {
                var me = this,
                    rec = me._buildRec('expand',arguments, 'direction,animate,timeout', {});
 
                if ('animate' in rec.args) {
                    rec.args[0] = rec.args.animate;
                    delete rec.args.animate;
                }
 
                me.play([rec]);
 
                return me;
            },
 
            fn: function () {
                var cmp = this.getComponent();
 
                cmp.expand.apply(cmp, this.args);
            }
        },
 
        /**
         * @method collapse
         * Schedules this panel to collapse. All arguments passed to this method will be
         * forwarded to the `collapse` method of the panel at the appropriate time so
         * consult the documentation for the actual framework and version you are using
         * for parameter details.
         *
         * @param {String} [direction] The direction to collapse towards. Must be one of
         *
         *   - Ext.Component.DIRECTION_TOP
         *   - Ext.Component.DIRECTION_RIGHT
         *   - Ext.Component.DIRECTION_BOTTOM
         *   - Ext.Component.DIRECTION_LEFT
         *
         * Defaults to the panel's `collapseDirection` config.
         *
         * @param {Boolean} [animate] True to animate the transition, else false
         * (defaults to the value of the `animCollapse` panel config). May also be specified
         * as the animation duration in milliseconds.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @return {ST.future.Panel} this
         * @chainable
         */
        collapse: {
            addEvent: function () {
                var me = this,
                    rec = me._buildRec('collapse',arguments, 'direction,animate,timeout', {});
 
                if ('direction' in rec.args) {
                    rec.args[0] = rec.args.direction;
                    delete rec.args.direction;
 
                    if ('animate' in rec.args) {
                        rec.args[1] = rec.args.animate;
                        delete rec.args.animate;
                    }
                }
 
                me.play([rec]);
 
                return me;
            },
            fn: function () {
                var cmp = this.getComponent();
 
                cmp.collapse.apply(cmp, this.args);
            }
        }
    }
});
 
/**
 * Returns a {@link ST.future.Panel future panel} used to queue operations for
 * when that panel becomes available.
 * @param {String} locator See {@link ST.Locator} for supported syntax.
 * @param {Number} [timeout] The maximum time (in milliseconds) to wait for the panel.
 * @return {ST.future.Panel}
 * @method panel
 * @member ST
 */
 
/**
 * @private
 * @class ST.future.SelectionModel
 * Mixin to provide selection-related capabilities to grid and dataviews. 
 */
ST.future.define('SelectionModel', {
    factoryable: false,
    valueProperty: false,
    playables: {
        /**
         * @method selected
         * Waits for the given records (by id) to be selected
         *
         *      ST.dataView('@someDataView').
         *          selected([1, 3]).
         *          and(function (dataView) {
         *              // 2 records are now selected
         *          });
         *
         * @param {Number/Number[]} id The ids of the records to select.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        selected: {
            params: 'id',
            is: function () {
                var id = this.args.id,
                    config = {
                        ids: ST.isArray(id) ? id : [id],
                        type: 'id',
                        mode: 'select'
                    };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['select']
        },
 
        /**
         * @method selectedAt
         * Waits for the given records (by index) to be selected
         *
         *      ST.dataView('@someDataView').
         *          selectedAt([1, 3]).
         *          and(function (dataView) {
         *              // 2 records are now selected
         *          });
         *
         * @param {Number/Number[]} index The indexes of the records to select.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        selectedAt: {
            params: 'index',
            is: function (index) {
                var index = this.args.index,
                    config = {
                        indexes: ST.isArray(index) ? index : [index],
                        type: 'index',
                        mode: 'select'
                    };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['select']
        },
 
        /**
         * @method selectedRange
         * Waits for the given records (by range of indexes) to be selected
         *
         *      ST.dataView('@someDataView').
         *          selectedRange(15, 45).
         *          and(function (dataView) {
         *              // range of records are now selected
         *          });
         *
         * @param {Number} start The starting index of the records to select.
         * @param {Number} [end] The ending index of the records to select. 
         * If not specified, the remainder of the available records will be selected
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        selectedRange: {
            params: 'start,end',
            is: function (start, end) {
                var config = {
                    start: this.args.start,
                    end: this.args.end,
                    type: 'range',
                    mode: 'select'
                };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['select']
        },
 
        /**
         * @method selectedWith
         * Waits for the given records (by simple query) to be selected
         *
         *      ST.dataView('@someDataView').
         *          selectedWith('name', 'Doug').
         *          and(function (dataView) {
         *              // matching records are now selected
         *          });
         *
         * @param {String} propertyName The name of the property in the record against which to query.
         * @param {String} propertyValue The value against which to query.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        selectedWith: {
            params: 'propertyName,propertyValue',
            is: function (propertyName, propertyValue) {
                var config = {
                    propertyName: this.args.propertyName,
                    propertyValue: this.args.propertyValue,
                    type: 'query',
                    mode: 'select'
                };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['select']
        },
 
        /**
         * @method deselected
         * Waits for the given records (by id) to be deselected
         *
         *      ST.dataView('@someDataView').
         *          ... select records ...
         *          deselected([1, 3]).
         *          and(function (dataView) {
         *              // 2 records are now deselected
         *          });
         *
         * @param {Number/Number[]} id The ids of the records to deselect.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        deselected: {
            params: 'id',
            is: function (id) {
                var id = this.args.id,
                    config = {
                        ids: ST.isArray(id) ? id : [id],
                        type: 'id',
                        mode: 'deselect'
                    };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['deselect']
        },
 
        /**
         * @method deselectedAt
         * Waits for the given records (by index) to be deselected
         *
         *      ST.dataView('@someDataView').
         *          ... select records ...
         *          deselectedAt([1, 3]).
         *          and(function (dataView) {
         *              // 2 records are now deselected
         *          });
         *
         * @param {Number/Number[]} index The indexes of the records to deselect.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        deselectedAt: {
            params: 'index',
            is: function (index) {
                var index = this.args.index,
                    config = {
                        indexes: ST.isArray(index) ? index : [index],
                        type: 'index',
                        mode: 'deselect'
                    };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['deselect']
        },
 
        /**
         * @method deselectedRange
         * Waits for the given records (by range of indexes) to be deselected
         *
         *      ST.dataView('@someDataView').
         *          ... select records ...
         *          deselectedRange(15, 45).
         *          and(function (dataView) {
         *              // range of records are now deselected
         *          });
         *
         * @param {Number} start The starting index of the records to deselect.
         * @param {Number} [end] The ending index of the records to deselect. 
         * If not specified, the remainder of the available records will be deselected
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        deselectedRange: {
            params: 'start,end',
            is: function (start, end) {
                var config = {
                    start: this.args.start,
                    end: this.args.end,
                    type: 'range',
                    mode: 'deselect'
                };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['deselect']
        },
 
        /**
         * @method deselectedWith
         * Waits for the given records (by simple query) to be deselected
         *
         *      ST.dataView('@someDataView').
         *          ... select records ...
         *          deselectedWith('name', 'Doug').
         *          and(function (dataView) {
         *              // matching records are now deselected
         *          });
         *
         * @param {String} propertyName The name of the property in the record against which to query.
         * @param {String} propertyValue The value against which to query.
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         * @chainable
         */
        deselectedWith: {
            params: 'propertyName,propertyValue',
            is: function (propertyName, propertyValue) {
                var config = {
                    propertyName: this.args.propertyName,
                    propertyValue: this.args.propertyValue,
                    type: 'query',
                    mode: 'deselect'
                };
 
                return ST.future.SelectionModel._validateSelections(this.getComponent(), config);
            },
            wait: ['deselect']
        },
 
        /**
         * @private
         * When the target is ready, this will execute the appropriate selection method based on the passed configuration
         * @param {Object} config The configuration object that will provide the necessary information to influence the selection
         * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
         */
        _doSelect: {
            addEvent: function (config, timeout) {
                var me = this,
                    rec = me._buildRec('_doSelect', arguments, 'config,timeout', {
                        futureData: {
                            select: config,
                            scoped: true
                        }
                    });
 
                me.play([rec]);
            },
            fn: function () {
                var cmp = this.getComponent(),
                    selModel = ST.isClassic ? cmp.getSelectionModel() : cmp,
                    opts = this.args.config,
                    records = ST.future.SelectionModel._getRecords(cmp, opts);
 
                if (opts.mode === 'select') {
                    selModel.select(records, opts.keepExisting);
                } else {
                    selModel.deselect(records);
                }
 
            }
        }
    },
 
    /**
     * Selects the requested record(s) given the record's `idProperty`.
     * @param {String/String[]} id The `idProperty` of the record(s) to select.
     * @param {Boolean} [keepExisting] `true` to preserve existing selections (default: `false`)
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    select: function (id, keepExisting, timeout) {
        return this.selectBy({
            ids: ST.isArray(id) ? id : [id],
            keepExisting: keepExisting,
            type: 'id'
        }, timeout);
    },
 
    /**
     * Selects the requested record(s) by index.
     * @param {String/String[]} index The index of the record(s) to select.
     * @param {Boolean} [keepExisting] `true` to preserve existing selections (default: `false`)
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    selectAt: function (index, keepExisting, timeout) {
        return this.selectBy({
            indexes: ST.isArray(index) ? index : [index],
            keepExisting: keepExisting,
            type: 'index'
        }, timeout);
    },
 
    /**
     * Selects the requested record(s) by index range.
     * @param {Number} start The starting index for the selection.
     * @param {Number} [end] The ending index for the selection. 
     * If not specified, the full range from the starting index will be included
     * @param {Boolean} [keepExisting] `true` to preserve existing selections (default: `false`)
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    selectRange: function (start, end, keepExisting, timeout) {
        return this.selectBy({
            start: start,
            end: end,
            keepExisting: keepExisting,
            type: 'range'
        }, timeout);
    },
 
    /**
     * Selects the requested record(s) by a simple property/value query.
     * @param {String} propertyName The name of the property by which to query.
     * @param {String} propertyValue The value by which to query.
     * @param {Boolean} [keepExisting] `true` to preserve existing selections (default: `false`)
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    selectWith: function (propertyName, propertyValue, keepExisting, timeout) {
        return this.selectBy({
            propertyName: propertyName,
            propertyValue: propertyValue,
            keepExisting: keepExisting,
            type: 'query'
        }, timeout);
    },
 
    /**
     * Selects all available records
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    selectAll: function (timeout) {
        return this.selectBy({
            type: 'all'
        }, timeout);
    },
 
    /**
     * Deselects the requested record(s) given the record's `idProperty`.
     * @param {String/String[]} id The `idProperty` of the record(s) to deselect.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    deselect: function (id, timeout) {
        return this.deselectBy({
            ids: ST.isArray(id) ? id : [id],
            type: 'id'
        }, timeout);
    },
 
    /**
     * Deselects the requested record(s) by index.
     * @param {String/String[]} index The index of the record(s) to deselect.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    deselectAt: function (index, timeout) {
        return this.deselectBy({
            indexes: ST.isArray(index) ? index : [index],
            type: 'index'
        }, timeout);
    },
 
    /**
     * Deselects the requested record(s) by index range.
     * @param {Number} start The starting index for the deselection.
     * @param {Number} [end] The ending index for the deselection. 
     * If not specified, the full range from the starting index will be included.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    deselectRange: function (start, end, timeout) {
        return this.deselectBy({
            start: start,
            end: end,
            type: 'range'
        }, timeout);
    },
 
    /**
     * Deselects the requested record(s) by a simple property/value query.
     * @param {String} propertyName The name of the property by which to query.
     * @param {String} propertyValue The value by which to query.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    deselectWith: function (propertyName, propertyValue, timeout) {
        return this.deselectBy({
            propertyName: propertyName,
            propertyValue: propertyValue,
            type: 'query'
        }, timeout);
    },
 
    /**
     * Deselects all selected records
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    deselectAll: function (timeout) {
        return this.deselectBy({
            type: 'all'
        }, timeout);
    },
 
    /**
     * Selects records given a config object that specified the match criteria.
     * @param {Object} config Configuration options for the selection.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    selectBy: function (config, timeout) {
        config.mode = 'select';
 
        this._doSelect(config, timeout);
 
        return this;
    },
 
    /**
     * Deselects records given a config object that specified the match criteria.
     * @param {Object} config Configuration options for the deselection.
     * @param {Number} [timeout] The maximum time (in milliseconds) to wait.
     * @chainable
     */
    deselectBy: function (config, timeout) {
        config.mode = 'deselect';
 
        this._doSelect(config, timeout);
 
        return this;
    },
 
    statics: {
        /**
         * @private
         * Validates current selections against the passed configuration options
         * @param {Ext.Component} cmp The Ext JS component on which to perform the action
         * @param {Object} config Configuration for validation of selections
         * @return {Boolean}
         */
        _validateSelections: function (cmp, config) {
            var selections = ST.isClassic ? cmp.getSelectionModel().getSelection() : cmp.getSelections(),
                records = ST.future.SelectionModel._getRecords(cmp, config),
                expectedLength = (config.ids && config.ids.length) || (config.indexes && config.indexes.length),
                matchCount = 0,
                useLength = config.mode === 'select' ? true : false,
                len = records.length,
                i;
 
            // if we have an expected number of records (id, index searches), but the length of those 
            // is not the same as the number of found records, this is a failure and we can abort 
            if (ST.isNumber(expectedLength) && expectedLength !== len) {
                return false;
            }
 
            for (i=0; i<len; i++) {
                if (selections.indexOf(records[i]) !== -1) {
                    matchCount++;
                }
            }
 
            return matchCount === (useLength ? len : 0);        
        },
 
        /**
         * @private
         * Utility to retrieve collection of records based on various criteria
         * @param {Ext.Component} cmp The Ext JS component on which to perform the action
         * @param {Object} options Configuration options to influence how records are retrieved
         * @return {Array}
         */
        _getRecords: function (cmp, options) {
            var store = cmp.getStore(),
                type = options.type,
                ids = options.ids,
                indexes = options.indexes,
                start = options.