/**
 * @class Ext.form.field.Picker
 * @extends Ext.form.field.Trigger
 * <p>An abstract class for fields that have a single trigger which opens a "picker" popup below
 * the field, e.g. a combobox menu list or a date picker. It provides a base implementation for
 * toggling the picker's visibility when the trigger is clicked, as well as keyboard navigation
 * and some basic events. Sizing and alignment of the picker can be controlled via the {@link #matchFieldWidth}
 * and {@link #pickerAlign}/{@link #pickerOffset} config properties respectively.</p>
 * <p>You would not normally use this class directly, but instead use it as the parent class for
 * a specific picker field implementation. Subclasses must implement the {@link #createPicker} method
 * to create a picker component appropriate for the field.</p>
 *
 * @xtype pickerfield
 * @constructor
 * Create a new picker field
 * @param {Object} config
 */

Ext.define('Ext.form.field.Picker', {
    extend
: 'Ext.form.field.Trigger',
   
alias: 'widget.pickerfield',
    alternateClassName
: 'Ext.form.Picker',
    requires
: ['Ext.util.KeyNav'],

    /**
     * @cfg {Boolean} matchFieldWidth
     * Whether the picker dropdown's width should be explicitly set to match the width of the field.
     * Defaults to <tt>true</tt>.
     */

    matchFieldWidth
: true,

    /**
     * @cfg {String} pickerAlign
     * The {@link Ext.core.Element#alignTo alignment position} with which to align the picker. Defaults
     * to <tt>"tl-bl?"</tt>
     */

    pickerAlign
: 'tl-bl?',

    /**
     * @cfg {Array} pickerOffset
     * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
     * Defaults to undefined.
     */


    /**
     * @cfg {String} openCls
     * A class to be added to the field's {@link #bodyEl} element when the picker is opened. Defaults
     * to 'x-pickerfield-open'.
     */

    openCls
: Ext.baseCSSPrefix + 'pickerfield-open',

    /**
     * @property isExpanded
     * @type Boolean
     * True if the picker is currently expanded, false if not.
     */


    /**
     * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
     * the field can only have its value set via selecting a value from the picker. In this state, the picker
     * can also be opened by clicking directly on the input field itself.
     * (defaults to <tt>true</tt>).
     */

    editable
: true,


    initComponent
: function() {
       
this.callParent();

       
// Custom events
       
this.addEvents(
            /**
             * @event expand
             * Fires when the field's picker is expanded.
             * @param {Ext.form.field.Picker} field This field instance
             */

           
'expand',
            /**
             * @event collapse
             * Fires when the field's picker is collapsed.
             * @param {Ext.form.field.Picker} field This field instance
             */

           
'collapse',
            /**
             * @event select
             * Fires when a value is selected via the picker.
             * @param {Ext.form.field.Picker} field This field instance
             * @param {Mixed} value The value that was selected. The exact type of this value is dependent on
             * the individual field and picker implementations.
             */

           
'select'
       
);
   
},


    initEvents
: function() {
       
var me = this;
        me
.callParent();

       
// Add handlers for keys to expand/collapse the picker
        me
.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
            down
: function() {
               
if (!me.isExpanded) {
                   
// Don't call expand() directly as there may be additional processing involved before
                   
// expanding, e.g. in the case of a ComboBox query.
                    me
.onTriggerClick();
               
}
           
},
            esc
: me.collapse,
            scope
: me,
            forceKeyDown
: true
       
});

       
// Non-editable allows opening the picker by clicking the field
       
if (!me.editable) {
            me
.mon(me.inputEl, 'click', me.onTriggerClick, me);
       
}

       
// Disable native browser autocomplete
       
if (Ext.isGecko) {
            me
.inputEl.dom.setAttribute('autocomplete', 'off');
       
}
   
},


    /**
     * Expand this field's picker dropdown.
     */

    expand
: function() {
       
var me = this,
            bodyEl
, picker, collapseIf;

       
if (me.rendered && !me.isExpanded && !me.isDestroyed) {
            bodyEl
= me.bodyEl;
            picker
= me.getPicker();
            collapseIf
= me.collapseIf;

           
// show the picker and set isExpanded flag
            picker
.show();
            me
.isExpanded = true;
            me
.alignPicker();
            bodyEl
.addCls(me.openCls);

           
// monitor clicking and mousewheel
            me
.mon(Ext.getDoc(), {
                mousewheel
: collapseIf,
                mousedown
: collapseIf,
                scope
: me
           
});

            me
.fireEvent('expand', me);
            me
.onExpand();
       
}
   
},

    onExpand
: Ext.emptyFn,

    /**
     * @protected
     * Aligns the picker to the
     */

    alignPicker
: function() {
       
var me = this,
            picker
, isAbove,
            aboveSfx
= '-above';

       
if (this.isExpanded) {
            picker
= me.getPicker();
           
if (me.matchFieldWidth) {
               
// Auto the height (it will be constrained by min and max width) unless there are no records to display.
                picker
.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
           
}
           
if (picker.isFloating()) {
                picker
.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);

               
// add the {openCls}-above class if the picker was aligned above
               
// the field due to hitting the bottom of the viewport
                isAbove
= picker.el.getY() < me.inputEl.getY();
                me
.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
                picker
.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
           
}
       
}
   
},

    /**
     * Collapse this field's picker dropdown.
     */

    collapse
: function() {
       
if (this.isExpanded && !this.isDestroyed) {
           
var me = this,
                openCls
= me.openCls,
                picker
= me.picker,
                doc
= Ext.getDoc(),
                collapseIf
= me.collapseIf,
                aboveSfx
= '-above';

           
// hide the picker and set isExpanded flag
            picker
.hide();
            me
.isExpanded = false;

           
// remove the openCls
            me
.bodyEl.removeCls([openCls, openCls + aboveSfx]);
            picker
.el.removeCls(picker.baseCls + aboveSfx);

           
// remove event listeners
            doc
.un('mousewheel', collapseIf, me);
            doc
.un('mousedown', collapseIf, me);

            me
.fireEvent('collapse', me);
            me
.onCollapse();
       
}
   
},

    onCollapse
: Ext.emptyFn,


    /**
     * @private
     * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
     */

    collapseIf
: function(e) {
       
var me = this;
       
if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
            me
.collapse();
       
}
   
},

    /**
     * Return a reference to the picker component for this field, creating it if necessary by
     * calling {@link #createPicker}.
     * @return {Ext.Component} The picker component
     */

    getPicker
: function() {
       
var me = this;
       
return me.picker || (me.picker = me.createPicker());
   
},

    /**
     * Create and return the component to be used as this field's picker. Must be implemented
     * by subclasses of Picker.
     * @return {Ext.Component} The picker component
     */

    createPicker
: Ext.emptyFn,

    /**
     * Handles the trigger click; by default toggles between expanding and collapsing the
     * picker component.
     */

    onTriggerClick
: function() {
       
var me = this;
       
if (!me.readOnly && !me.disabled) {
           
if (me.isExpanded) {
                me
.collapse();
           
} else {
                me
.expand();
           
}
            me
.inputEl.focus();
       
}
   
},

    mimicBlur
: function(e) {
       
var me = this,
            picker
= me.picker;
       
// ignore mousedown events within the picker element
       
if (!picker || !e.within(picker.el, false, true)) {
            me
.callParent(arguments);
       
}
   
},

    onDestroy
: function(){
       
var me = this;
       
Ext.destroy(me.picker, me.keyNav);
        me
.callParent();
   
}

});