/**
* @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();
}
});