/** * @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 }); Ext.EventManager.onWindowResize(me.alignPicker, 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); Ext.EventManager.removeResizeListener(me.alignPicker, 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.EventManager.removeResizeListener(me.alignPicker, me); Ext.destroy(me.picker, me.keyNav); me.callParent(); } });