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