X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/form/field/Trigger.js diff --git a/src/form/field/Trigger.js b/src/form/field/Trigger.js new file mode 100644 index 00000000..60dabb9a --- /dev/null +++ b/src/form/field/Trigger.js @@ -0,0 +1,412 @@ +/** + * @class Ext.form.field.Trigger + * @extends Ext.form.field.Text + *

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:

+ *

+    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',
+        }]
+    });
+
+ * + *

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.

+ * + * @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: [ + 'name="{name}" ', + 'size="{size}" ', + 'tabIndex="{tabIdx}" ', + 'class="{fieldCls} {typeCls}" autocomplete="off" />', + '', + { + 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 triggerCls will be appended 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 true to hide the trigger element and display only the base + * text field (defaults to false) + */ + hideTrigger: false, + + /** + * @cfg {Boolean} editable false 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 true). + */ + editable: true, + + /** + * @cfg {Boolean} readOnly true 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 false) + */ + readOnly: false, + + /** + * @cfg {Boolean} selectOnFocus true to select any existing text in the field immediately on focus. + * Only applies when {@link #editable editable} = true (defaults to false). + */ + + /** + * @cfg {Boolean} repeatTriggerClick true to attach a {@link Ext.util.ClickRepeater click repeater} + * to the trigger. Defaults to false. + */ + 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 triggerCls 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 triggerCls 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 + */ +});