/** * @class Ext.form.field.Trigger * @extends Ext.form.field.Text * <p>Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default). * The trigger has no default action, so you must assign a function to implement the trigger click handler by * overriding {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox * for which you can provide a custom implementation. * {@img Ext.form.field.Trigger/Ext.form.field.Trigger.png Ext.form.field.Trigger component} * For example:</p> * <pre><code> Ext.define('Ext.ux.CustomTrigger', { extend: 'Ext.form.field.Trigger', alias: 'widget.customtrigger', // override onTriggerClick onTriggerClick: function() { Ext.Msg.alert('Status', 'You clicked my trigger!'); } }); Ext.create('Ext.form.FormPanel', { title: 'Form with TriggerField', bodyPadding: 5, width: 350, renderTo: Ext.getBody(), items:[{ xtype: 'customtrigger', fieldLabel: 'Sample Trigger', emptyText: 'click the trigger', }] }); </code></pre> * * <p>However, in general you will most likely want to use Trigger as the base class for a reusable component. * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.</p> * * @constructor * Create a new Trigger field. * @param {Object} config Configuration options (valid {@Ext.form.field.Text} config options will also be applied * to the base Text field) * @xtype triggerfield */ Ext.define('Ext.form.field.Trigger', { extend:'Ext.form.field.Text', alias: ['widget.triggerfield', 'widget.trigger'], requires: ['Ext.core.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'], alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'], fieldSubTpl: [ '<input id="{id}" type="{type}" ', '<tpl if="name">name="{name}" </tpl>', '<tpl if="size">size="{size}" </tpl>', '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>', 'class="{fieldCls} {typeCls}" autocomplete="off" />', '<div class="{triggerWrapCls}" role="presentation">', '{triggerEl}', '<div class="{clearCls}" role="presentation"></div>', '</div>', { compiled: true, disableFormats: true } ], /** * @cfg {String} triggerCls * An additional CSS class used to style the trigger button. The trigger will always get the * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified. * Defaults to undefined. */ /** * @cfg {String} triggerBaseCls * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be * appended in addition to this class. */ triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger', /** * @cfg {String} triggerWrapCls * The CSS class that is added to the div wrapping the trigger button(s). */ triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap', /** * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base * text field (defaults to <tt>false</tt>) */ hideTrigger: false, /** * @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 an action invoked by the trigger. (defaults to <tt>true</tt>). */ editable: true, /** * @cfg {Boolean} readOnly <tt>true</tt> to prevent the user from changing the field, and * hides the trigger. Supercedes the editable and hideTrigger options if the value is true. * (defaults to <tt>false</tt>) */ readOnly: false, /** * @cfg {Boolean} selectOnFocus <tt>true</tt> to select any existing text in the field immediately on focus. * Only applies when <tt>{@link #editable editable} = true</tt> (defaults to <tt>false</tt>). */ /** * @cfg {Boolean} repeatTriggerClick <tt>true</tt> to attach a {@link Ext.util.ClickRepeater click repeater} * to the trigger. Defaults to <tt>false</tt>. */ repeatTriggerClick: false, /** * @hide * @method autoSize */ autoSize: Ext.emptyFn, // private monitorTab: true, // private mimicing: false, // private triggerIndexRe: /trigger-index-(\d+)/, componentLayout: 'triggerfield', initComponent: function() { this.wrapFocusCls = this.triggerWrapCls + '-focus'; this.callParent(arguments); }, // private onRender: function(ct, position) { var me = this, triggerCls, triggerBaseCls = me.triggerBaseCls, triggerWrapCls = me.triggerWrapCls, triggerConfigs = [], i; // triggerCls is a synonym for trigger1Cls, so copy it. // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the // single triggerCls config. Should rethink this, perhaps something more structured like a list of // trigger config objects that hold cls, handler, etc. if (!me.trigger1Cls) { me.trigger1Cls = me.triggerCls; } // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) { triggerConfigs.push({ cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '), role: 'button' }); } triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last'; Ext.applyIf(me.renderSelectors, { /** * @property triggerWrap * @type Ext.core.Element * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered. */ triggerWrap: '.' + triggerWrapCls }); Ext.applyIf(me.subTplData, { triggerWrapCls: triggerWrapCls, triggerEl: Ext.core.DomHelper.markup(triggerConfigs), clearCls: me.clearCls }); me.callParent(arguments); /** * @property triggerEl * @type Ext.CompositeElement * A composite of all the trigger button elements. Only set after the field has been rendered. */ me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom); me.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc(); me.initTrigger(); }, onEnable: function() { this.callParent(); this.triggerWrap.unmask(); }, onDisable: function() { this.callParent(); this.triggerWrap.mask(); }, afterRender: function() { this.callParent(); this.updateEditState(); }, updateEditState: function() { var me = this, inputEl = me.inputEl, triggerWrap = me.triggerWrap, noeditCls = Ext.baseCSSPrefix + 'trigger-noedit', displayed, readOnly; if (me.rendered) { if (me.readOnly) { inputEl.addCls(noeditCls); readOnly = true; displayed = false; } else { if (me.editable) { inputEl.removeCls(noeditCls); readOnly = false; } else { inputEl.addCls(noeditCls); readOnly = true; } displayed = !me.hideTrigger; } triggerWrap.setDisplayed(displayed); inputEl.dom.readOnly = readOnly; me.doComponentLayout(); } }, /** * Get the total width of the trigger button area. Only useful after the field has been rendered. * @return {Number} The trigger width */ getTriggerWidth: function() { var me = this, triggerWrap = me.triggerWrap, totalTriggerWidth = 0; if (triggerWrap && !me.hideTrigger && !me.readOnly) { me.triggerEl.each(function(trigger) { totalTriggerWidth += trigger.getWidth(); }); totalTriggerWidth += me.triggerWrap.getFrameWidth('lr'); } return totalTriggerWidth; }, setHideTrigger: function(hideTrigger) { if (hideTrigger != this.hideTrigger) { this.hideTrigger = hideTrigger; this.updateEditState(); } }, /** * @param {Boolean} editable True to allow the user to directly edit the field text * Allow or prevent the user from directly editing the field text. If false is passed, * the user will only be able to modify the field using the trigger. Will also add * a click event to the text field which will call the trigger. This method * is the runtime equivalent of setting the 'editable' config option at config time. */ setEditable: function(editable) { if (editable != this.editable) { this.editable = editable; this.updateEditState(); } }, /** * @param {Boolean} readOnly True to prevent the user changing the field and explicitly * hide the trigger. * Setting this to true will superceed settings editable and hideTrigger. * Setting this to false will defer back to editable and hideTrigger. This method * is the runtime equivalent of setting the 'readOnly' config option at config time. */ setReadOnly: function(readOnly) { if (readOnly != this.readOnly) { this.readOnly = readOnly; this.updateEditState(); } }, // private initTrigger: function() { var me = this, triggerWrap = me.triggerWrap, triggerEl = me.triggerEl; if (me.repeatTriggerClick) { me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, { preventDefault: true, handler: function(cr, e) { me.onTriggerWrapClick(e); } }); } else { me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me); } triggerEl.addClsOnOver(me.triggerBaseCls + '-over'); triggerEl.each(function(el, c, i) { el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over'); }); triggerEl.addClsOnClick(me.triggerBaseCls + '-click'); triggerEl.each(function(el, c, i) { el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click'); }); }, // private onDestroy: function() { var me = this; Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl'); delete me.doc; me.callParent(); }, // private onFocus: function() { var me = this; this.callParent(); if (!me.mimicing) { me.bodyEl.addCls(me.wrapFocusCls); me.mimicing = true; me.mon(me.doc, 'mousedown', me.mimicBlur, me, { delay: 10 }); if (me.monitorTab) { me.on('specialkey', me.checkTab, me); } } }, // private checkTab: function(me, e) { if (!this.ignoreMonitorTab && e.getKey() == e.TAB) { this.triggerBlur(); } }, // private onBlur: Ext.emptyFn, // private mimicBlur: function(e) { if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) { this.triggerBlur(); } }, // private triggerBlur: function() { var me = this; me.mimicing = false; me.mun(me.doc, 'mousedown', me.mimicBlur, me); if (me.monitorTab && me.inputEl) { me.un('specialkey', me.checkTab, me); } Ext.form.field.Trigger.superclass.onBlur.call(me); if (me.bodyEl) { me.bodyEl.removeCls(me.wrapFocusCls); } }, beforeBlur: Ext.emptyFn, // private // This should be overridden by any subclass that needs to check whether or not the field can be blurred. validateBlur: function(e) { return true; }, // private // process clicks upon triggers. // determine which trigger index, and dispatch to the appropriate click handler onTriggerWrapClick: function(e) { var me = this, t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null), match = t && t.className.match(me.triggerIndexRe), idx, triggerClickMethod; if (match && !me.readOnly) { idx = parseInt(match[1], 10); triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick; if (triggerClickMethod) { triggerClickMethod.call(me, e); } } }, /** * The function that should handle the trigger's click event. This method does nothing by default * until overridden by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for * sample implementations. * @method * @param {Ext.EventObject} e */ onTriggerClick: Ext.emptyFn /** * @cfg {Boolean} grow @hide */ /** * @cfg {Number} growMin @hide */ /** * @cfg {Number} growMax @hide */ });