3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
17 * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
18 * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
19 * can provide a custom implementation.
24 * Ext.define('Ext.ux.CustomTrigger', {
25 * extend: 'Ext.form.field.Trigger',
26 * alias: 'widget.customtrigger',
28 * // override onTriggerClick
29 * onTriggerClick: function() {
30 * Ext.Msg.alert('Status', 'You clicked my trigger!');
34 * Ext.create('Ext.form.FormPanel', {
35 * title: 'Form with TriggerField',
38 * renderTo: Ext.getBody(),
40 * xtype: 'customtrigger',
41 * fieldLabel: 'Sample Trigger',
42 * emptyText: 'click the trigger',
46 * However, in general you will most likely want to use Trigger as the base class for a reusable component.
47 * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
49 Ext.define('Ext.form.field.Trigger', {
50 extend:'Ext.form.field.Text',
51 alias: ['widget.triggerfield', 'widget.trigger'],
52 requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
53 alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
55 // note: {id} here is really {inputId}, but {cmpId} is available
57 '<input id="{id}" type="{type}" ',
58 '<tpl if="name">name="{name}" </tpl>',
59 '<tpl if="size">size="{size}" </tpl>',
60 '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
61 'class="{fieldCls} {typeCls}" autocomplete="off" />',
62 '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
64 '<div class="{clearCls}" role="presentation"></div>',
73 * @cfg {String} triggerCls
74 * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
75 * by default and triggerCls will be **appended** if specified.
79 * @cfg {String} [triggerBaseCls='x-form-trigger']
80 * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
81 * addition to this class.
83 triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
86 * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
87 * The CSS class that is added to the div wrapping the trigger button(s).
89 triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
92 * @cfg {Boolean} hideTrigger
93 * true to hide the trigger element and display only the base text field
98 * @cfg {Boolean} editable
99 * false to prevent the user from typing text directly into the field; the field can only have its value set via an
100 * action invoked by the trigger.
105 * @cfg {Boolean} readOnly
106 * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
107 * options if the value is true.
112 * @cfg {Boolean} [selectOnFocus=false]
113 * true to select any existing text in the field immediately on focus. Only applies when
114 * {@link #editable editable} = true
118 * @cfg {Boolean} repeatTriggerClick
119 * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
121 repeatTriggerClick: false,
128 autoSize: Ext.emptyFn,
134 triggerIndexRe: /trigger-index-(\d+)/,
136 componentLayout: 'triggerfield',
138 initComponent: function() {
139 this.wrapFocusCls = this.triggerWrapCls + '-focus';
140 this.callParent(arguments);
144 onRender: function(ct, position) {
147 triggerBaseCls = me.triggerBaseCls,
148 triggerWrapCls = me.triggerWrapCls,
152 // triggerCls is a synonym for trigger1Cls, so copy it.
153 // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
154 // single triggerCls config. Should rethink this, perhaps something more structured like a list of
155 // trigger config objects that hold cls, handler, etc.
156 if (!me.trigger1Cls) {
157 me.trigger1Cls = me.triggerCls;
160 // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
161 for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
162 triggerConfigs.push({
163 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
167 triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
170 * @property {Ext.Element} triggerWrap
171 * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
173 me.addChildEls('triggerWrap');
175 Ext.applyIf(me.subTplData, {
176 triggerWrapCls: triggerWrapCls,
177 triggerEl: Ext.DomHelper.markup(triggerConfigs),
178 clearCls: me.clearCls
181 me.callParent(arguments);
184 * @property {Ext.CompositeElement} triggerEl
185 * A composite of all the trigger button elements. Only set after the field has been rendered.
187 me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
189 me.doc = Ext.getDoc();
193 onEnable: function() {
195 this.triggerWrap.unmask();
198 onDisable: function() {
200 this.triggerWrap.mask();
203 afterRender: function() {
205 this.updateEditState();
206 this.triggerEl.unselectable();
209 updateEditState: function() {
211 inputEl = me.inputEl,
212 triggerWrap = me.triggerWrap,
213 noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
219 inputEl.addCls(noeditCls);
224 inputEl.removeCls(noeditCls);
227 inputEl.addCls(noeditCls);
230 displayed = !me.hideTrigger;
233 triggerWrap.setDisplayed(displayed);
234 inputEl.dom.readOnly = readOnly;
235 me.doComponentLayout();
240 * Get the total width of the trigger button area. Only useful after the field has been rendered.
241 * @return {Number} The trigger width
243 getTriggerWidth: function() {
245 triggerWrap = me.triggerWrap,
246 totalTriggerWidth = 0;
247 if (triggerWrap && !me.hideTrigger && !me.readOnly) {
248 me.triggerEl.each(function(trigger) {
249 totalTriggerWidth += trigger.getWidth();
251 totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
253 return totalTriggerWidth;
256 setHideTrigger: function(hideTrigger) {
257 if (hideTrigger != this.hideTrigger) {
258 this.hideTrigger = hideTrigger;
259 this.updateEditState();
264 * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
265 * option at config time.
266 * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
267 * will only be able to modify the field using the trigger. Will also add a click event to the text field which
268 * will call the trigger.
270 setEditable: function(editable) {
271 if (editable != this.editable) {
272 this.editable = editable;
273 this.updateEditState();
278 * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
279 * option at config time.
280 * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
281 * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
284 setReadOnly: function(readOnly) {
285 if (readOnly != this.readOnly) {
286 this.readOnly = readOnly;
287 this.updateEditState();
292 initTrigger: function() {
294 triggerWrap = me.triggerWrap,
295 triggerEl = me.triggerEl;
297 if (me.repeatTriggerClick) {
298 me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
299 preventDefault: true,
300 handler: function(cr, e) {
301 me.onTriggerWrapClick(e);
305 me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
308 triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
309 triggerEl.each(function(el, c, i) {
310 el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
312 triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
313 triggerEl.each(function(el, c, i) {
314 el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
319 onDestroy: function() {
321 Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
327 onFocus: function() {
331 me.bodyEl.addCls(me.wrapFocusCls);
333 me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
337 me.on('specialkey', me.checkTab, me);
343 checkTab: function(me, e) {
344 if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
353 mimicBlur: function(e) {
354 if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
360 triggerBlur: function() {
363 me.mun(me.doc, 'mousedown', me.mimicBlur, me);
364 if (me.monitorTab && me.inputEl) {
365 me.un('specialkey', me.checkTab, me);
367 Ext.form.field.Trigger.superclass.onBlur.call(me);
369 me.bodyEl.removeCls(me.wrapFocusCls);
373 beforeBlur: Ext.emptyFn,
376 // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
377 validateBlur: function(e) {
382 // process clicks upon triggers.
383 // determine which trigger index, and dispatch to the appropriate click handler
384 onTriggerWrapClick: function(e) {
386 t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
387 match = t && t.className.match(me.triggerIndexRe),
391 if (match && !me.readOnly) {
392 idx = parseInt(match[1], 10);
393 triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
394 if (triggerClickMethod) {
395 triggerClickMethod.call(me, e);
401 * @method onTriggerClick
403 * The function that should handle the trigger's click event. This method does nothing by default until overridden
404 * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
405 * @param {Ext.EventObject} e
407 onTriggerClick: Ext.emptyFn
410 * @cfg {Boolean} grow @hide
413 * @cfg {Number} growMin @hide
416 * @cfg {Number} growMax @hide