/** * フィールドの下に「ピッカー」(コンボボックスメニューリストやデートピッカーなど)をポップアップ表示する単一のトリガーを持つ、フィールドの抽象クラスです。キーボードのナビゲーションや一部の基本的なイベントのほか、トリガーがクリックされるとピッカーの表示状態を切り替えるベースの実装を提供します。ピッカーのサイズと配置は、それぞれ{@link #matchFieldWidth}および{@link #pickerAlign}/{@link #pickerOffset}コンフィグのプロパティでコントロールできます。 * * 通常はこのクラスを直接使用することはなく、特定のピッカーフィールドを実装するための親クラスとして使用します。フィールドに適したピッカーコンポーネントを作成するため、サブクラスでは{@link #createPicker}メソッドを実装することが必要です。 */ Ext.define('Ext.form.field.Picker', { extend: 'Ext.form.field.Text', alias: 'widget.pickerfield', alternateClassName: 'Ext.form.Picker', requires: ['Ext.util.KeyNav'], config: { triggers: { picker: { handler: 'onTriggerClick', scope: 'this' } } }, /** * @cfg {Boolean} matchFieldWidth * ピッカードロップダウンの幅がフィールド幅と一致するようにするかどうか。デフォルト値はtrueです。 */ matchFieldWidth: true, /** * @cfg {String} pickerAlign * ピッカーを配置する{@link Ext.util.Positionable#alignTo 位置} です。デフォルトは"tl-bl?"です。 */ pickerAlign: 'tl-bl?', /** * @cfg {Number[]} pickerOffset * ピッカーを配置する際に{@link #pickerAlign}に加えて使用する[x,y]オフセットです。デフォルトはundefinedです。 */ /** * @cfg {String} [openCls='x-pickerfield-open'] * ピッカーを開く際にフィールドの{@link #bodyEl}要素に追加するクラスです。 */ openCls: Ext.baseCSSPrefix + 'pickerfield-open', /** * @property {Boolean} isExpanded * ピッカーが開いていたらture、そうでなければfalse。 */ /** * @cfg {Boolean} editable * falseを指定すると、フィールドに直接入力できないようにします。 フィールドはピッカーで値を選択するこによってしか値を変更できなくなります。この状態では、ピッカーは入力フィールドそのものを直接クリックしても開くようになります。 */ editable: true, /** * @cfg {String} triggerCls * トリガーボタンをスタイリングする追加のCSSクラス。トリガーは必ずクラス'x-form-trigger'を取得し、指定されている場合にはtriggerClsが付加されます。 */ /** * @event expand * ピッカーが開いたときに発火します。 * @param {Ext.form.field.Picker} field このフィールドのインスタンスです。 */ /** * @event collapse * フィールドのピッカーを閉じたときに発火します。 * @param {Ext.form.field.Picker} field このフィールドのインスタンスです。 */ /** * @event select * ピッカー内の値が選択されたときに発火します。 * @param {Ext.form.field.Picker} field このフィールドのインスタンスです。 * @param {Object} value 選択された値。この値の正確な型は個々のフィールドとピッカーの実装によります。 */ applyTriggers: function(triggers) { var me = this, picker = triggers.picker; if (!picker.cls) { picker.cls = me.triggerCls; } return me.callParent([triggers]); }, initEvents: function() { var me = this; me.callParent(); // Add handlers for keys to expand/collapse the picker me.keyNav = new Ext.util.KeyNav(me.inputEl, { down: me.onDownArrow, esc: { handler: me.onEsc, scope: me, defaultEventAction: false }, 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'); } }, // private onEsc: function(e) { if (Ext.isIE) { // Stop the esc key from "restoring" the previous value in IE // For example, type "foo". Highlight all the text, hit backspace. // Hit esc, "foo" will be restored. This behaviour doesn't occur // in any other browsers e.preventDefault(); } if (this.isExpanded) { this.collapse(); e.stopEvent(); } }, onDownArrow: function() { if (!this.isExpanded) { // Don't call expand() directly as there may be additional processing involved before // expanding, e.g. in the case of a ComboBox query. this.onTriggerClick(); } }, /** * このフィールドのピッカードロップダウンを開きます。 */ expand: function() { var me = this, bodyEl, picker, collapseIf; if (me.rendered && !me.isExpanded && !me.isDestroyed) { me.expanding = true; 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.on('resize', me.alignPicker, me); me.fireEvent('expand', me); me.onExpand(); delete me.expanding; } }, onExpand: Ext.emptyFn, /** * input要素にピッカーを揃えます。 * @protected */ alignPicker: function() { var me = this, bodyElWidth, picker = me.getPicker(); if (me.isExpanded) { if (me.matchFieldWidth) { bodyElWidth = me.bodyEl.getWidth(); // Auto the height (it will be constrained by min and max width) unless there are no records to display. picker.setWidth(bodyElWidth); } if (picker.isFloating()) { me.doAlign(); } } }, /** * クラスのデフォルトで使用しているピッカーのアライメントを適用します。 * @private */ doAlign: function(){ var me = this, picker = me.picker, aboveSfx = '-above', isAbove; // Align to the trigger wrap because the border isn't always on the input element, which // can cause the offset to be off me.picker.alignTo(me.triggerWrap, 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[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx); }, /** * フィールドのピッカードロップダウンを閉じます。 */ collapse: function() { var me = this; if (me.isExpanded && !me.isDestroyed && !me.destroying) { var 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.un('resize', me.alignPicker, me); me.fireEvent('collapse', me); me.onCollapse(); } }, onCollapse: Ext.emptyFn, /** * @private * ピッカーを折りたたむかどうかを確認するためにドキュメントのmousewheelとmousedownを実行します。 */ collapseIf: function(e) { var me = this; if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !me.owns(e.target)) { me.collapse(); } }, /** * このフィールドのピッカーコンポーネントへの参照を返します。 その際必要であれば{@link #createPicker}をコールして生成します。 * @return {Ext.Component} ピッカーコンポーネント。 */ getPicker: function() { var me = this, picker = me.picker; if (!picker) { me.picker = picker = me.createPicker(); // For upward component searches. picker.ownerCmp = me; } return me.picker; }, // @private // The CQ interface. Allow drilling down into the picker when it exists. // Important for determining whether an event took place in the bounds of some // higher level containing component. See AbstractComponent#owns getRefItems: function() { var result = []; if (this.picker) { result[0] = this.picker; } return result; }, /** * @method * このフィールドのピッカーとして使用されるコンポーネントを生成して返します。ピッカーのサブクラスにより実装されることが必要です。 */ createPicker: Ext.emptyFn, /** * トリガークリックを処理します。 デフォルトではピッカーの折りたたみと展開を切り替えます。 * @protected */ onTriggerClick: function(e) { var me = this; if (!me.readOnly && !me.disabled) { if (me.isExpanded) { me.collapse(); } else { me.expand(); } } }, onOtherFocus: function(dom) { if (this.hasFocus && !this.owns(dom)) { this.callParent([dom]); } }, beforeDestroy : function(){ var me = this, picker = me.picker; me.collapse(); me.callParent(); Ext.un('resize', me.alignPicker, me); Ext.destroy(me.keyNav, picker); if (picker) { delete me.picker; delete picker.pickerField; } } });