To develop this plugin we will first create the clear button that will be rendered over the text component, and will apply the CSS styles according to the configuration options. After that, we need to add several event handlers for the clear button, such as click
, mouseover
, mouseout
, mouseup
, and mousedown
, and also, several event handlers for the text component, such as destroy
, resize
, change
, mouseover
, and mouseout
. Let us now start coding:
Ext.define('Examples.plugin.ClearButton', { alias : 'plugin.clearbutton', hideClearButtonWhenEmpty : true, hideClearButtonWhenMouseOut : true, animateClearButton : true, clearOnEscape : true, clearButtonCls : 'ext-ux-clearbutton', textField : null, animateWithCss3 : false, constructor : function(cfg) { Ext.apply(this, cfg); this.callParent(arguments); }, init : function(textField) { this.textField = textField; if (!textField.rendered) { textField.on('afterrender', this.handleAfterRender, this); } else { this.handleAfterRender(); } }
In the preceding code snippet, you can see that we have defined several configuration options, and the required
init
function.
Now let us define the handleAfterRender
function:
handleAfterRender : function(textField) { this.isTextArea = (this.textField.inputEl.dom.type.toLowerCase() == 'textarea'), this.createClearButtonEl(); this.addListeners(); this.repositionClearButton(); this.updateClearButtonVisibility(); this.addEscListener(); }
Within this handleAfterRender
function, at first, we are checking whether the textfield is a textarea
or not, as we need to handle textarea
with custom functionality since this field may have a scrollbar. And then we call the createClearButtonEl
function to create the element and the DOM for the clear button.
Now let us define the createClearButtonEl
function:
createClearButtonEl : function() { var animateWithClass = this.animateClearButton &&this.animateWithCss3; this.clearButtonEl = this.textField.bodyEl.createChild({ tag : 'div', cls : this.clearButtonCls }); if (this.animateClearButton) { this.animateWithCss3 = this.supportsCssTransition(this.clearButtonEl); } if (this.animateWithCss3) { this.clearButtonEl.addCls(this.clearButtonCls + '-off'), } else { this.clearButtonEl.setStyle('visibility', 'hidden'), } }
In the preceding function the clear button has been created and assigned an animation, based on the configuration options. In this function we also checked whether the browser supports CSS3 transitions or not, by calling the supportsCssTransition
function.
Now, let us define the supportsCssTransition
function:
supportsCssTransition: function(el) { var styles = ['transitionProperty', 'WebkitTransitionProperty','MozTransitionProperty', 'OTransitionProperty','msTransitionProperty', 'KhtmlTransitionProperty']; var style = el.dom.style; for(var i = 0, length = styles.length; i < length; ++i) { if(style[styles[i]] !== 'undefined') { return true; } } return false; }
The next function we are calling within the handleAfterRender
function is the addListeners
function to add listeners to the field, its input element, and the clear button to handle resizing events such as mouseover
, mouseout
, and click
.
Now, let us define the addListeners
function:
addListeners: function() { var textField = this.textField; var bodyEl = textField.bodyEl; bodyEl.on('mouseover', this.handleMouseOverInputField, this); bodyEl.on('mouseout', this.handleMouseOutOfInputField, this); textField.on('destroy', this.handleDestroy, this); textField.on('resize', this.repositionClearButton, this); textField.on('change', function() { this.repositionClearButton(); this.updateClearButtonVisibility(); }, this); var clearButtonEl = this.clearButtonEl; clearButtonEl.on('mouseover', this.handleMouseOverClearButton,this); clearButtonEl.on('mouseout', this.handleMouseOutOfClearButton,this); clearButtonEl.on('mousedown', this.handleMouseDownOnClearButton,this); clearButtonEl.on('mouseup', this.handleMouseUpOnClearButton,this); clearButtonEl.on('click', this.handleMouseClickOnClearButton,this); }
Next we define the mouseover
event handler – handleMouseOverInputField
, and the
mouseout
event handler – handleMouseOutOfInputField
, for bodyEl
of textField
:
handleMouseOverInputField: function(event, htmlElement, object) { this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-over-input'), if (event.getRelatedTarget() == this.clearButtonEl.dom) { this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-over-button'), this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-down'), } this.updateClearButtonVisibility(); }, handleMouseOutOfInputField: function(event, htmlElement, object) { this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-over-input'), if (event.getRelatedTarget() == this.clearButtonEl.dom) { this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-over-button'), } this.updateClearButtonVisibility(); }
Now let us define the "destroy" event handler of textField
since when the field is destroyed, we also need to destroy the clear button element to prevent memory leaks:
handleDestroy: function() { this.clearButtonEl.destroy(); }
Now let us start defining the handlers for the clear button's mouseover
, mouseout
, mousedown
, mouseup
, and click
events:
handleMouseOverClearButton: function(event, htmlElement, object) { event.stopEvent(); if (this.textField.bodyEl.contains(event.getRelatedTarget())) { return; } this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-over-button'), this.updateClearButtonVisibility(); }, handleMouseOutOfClearButton: function(event, htmlElement, object){ event.stopEvent(); if (this.textField.bodyEl.contains(event.getRelatedTarget())) { return; } this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-over-button'), this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-down'), this.updateClearButtonVisibility(); }, handleMouseDownOnClearButton: function(event, htmlElement,object){ if (!this.isLeftButton(event)) { return; } this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-down'), }, handleMouseUpOnClearButton: function(event, htmlElement, object) { if (!this.isLeftButton(event)) { return; } this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-down'), }, handleMouseClickOnClearButton: function(event, htmlElement, object) { if (!this.isLeftButton(event)) { return; } this.textField.setValue(''), this.textField.focus(); }
The next function we will call within the handleAfterRender
function is the repositionClearButton
function, to reposition the clear button element based on the inputEl
element of textField
. Now, let us define this function:
repositionClearButton: function() { var clearButtonEl = this.clearButtonEl; if (!clearButtonEl) { return; } var clearButtonPosition = this.calculateClearButtonPosition(this.textField); clearButtonEl.dom.style.right = clearButtonPosition.right +'px'; clearButtonEl.dom.style.top = clearButtonPosition.top + 'px'; }
You can see that we get the clear button's position value by calling the calculateClearButtonPosition
function. This function calculates the position of the clear button, based on the inputEl
element of textField
. Now, let us define this function:
calculateClearButtonPosition: function(textField) { var positions = textField.inputEl.getBox(true, true); var top = positions.y; var right = positions.x; if (this.fieldHasScrollBar()) { right += Ext.getScrollBarWidth(); } if (this.textField.triggerWrap) { right += this.textField.getTriggerWidth(); } return { right: right, top: top }; }
You can see that we checked whether the field has a scrollbar or not, and if the field has a scrollbar, we add the value of the Ext.getScrollBarWidth
function to the right
position. Now, let us define the fieldHasScrollBar
function:
fieldHasScrollBar: function() { if (!this.isTextArea) { return false; } var inputEl = this.textField.inputEl; var overflowY = inputEl.getStyle('overflow-y'), if (overflowY == 'hidden' || overflowY == 'visible') { return false; } if (overflowY == 'scroll') { return true; } if (inputEl.dom.scrollHeight <= inputEl.dom.clientHeight) { return false; } return true; }
And then we called the updateClearButtonVisibility
function within the handleAfterRender
function for fixing the clear button's visibility:
updateClearButtonVisibility: function() { var oldVisible = this.isButtonCurrentlyVisible(); var newVisible = this.shouldButtonBeVisible(); var clearButtonEl = this.clearButtonEl; if (oldVisible != newVisible) { if(this.animateClearButton && this.animateWithCss3) { this.clearButtonEl.removeCls(this.clearButtonCls +(oldVisible ? '-on' : '-off')); clearButtonEl.addCls(this.clearButtonCls + (newVisible ? '-on' : '-off')); } else { clearButtonEl.stopAnimation(); clearButtonEl.setVisible(newVisible,this.animateClearButton); } clearButtonEl.setStyle('background-color',this.textField.inputEl.getStyle('background-color')); if (!(this.isTextArea && Ext.isGecko) && !Ext.isIE) { var deltaPaddingRight = clearButtonEl.getWidth() - this.clearButtonEl.getMargin('l'), var currentPaddingRight = this.textField.inputEl.getPadding('r'), var factor = (newVisible ? +1 : -1); this.textField.inputEl.dom.style.paddingRight = (currentPaddingRight + factor * deltaPaddingRight) + 'px'; } } }
You can see that we took the value of the current visible state, and what will be the new visible state, by calling the
isButtonCurrentlyVisible
and shouldButtonBeVisible
functions. The isButtonCurrentlyVisible
function is a wrapper around clearButtonEl.isVisible()
to handle the setVisible
animation that may still be in progress, and the shouldButtonBeVisible
function checks the configuration options and the current mouse status to determine whether the clear button should be visible or not. Now, let us define these functions:
isButtonCurrentlyVisible: function() { if (this.animateClearButton && this.animateWithCss3) { return this.clearButtonEl.hasCls(this.clearButtonCls + '-on'), } var cachedVisible = Ext.core.Element.data(this.clearButtonEl.dom, 'isVisible'), if (typeof(cachedVisible) == 'boolean') { return cachedVisible; } return this.clearButtonEl.isVisible(); }, shouldButtonBeVisible: function() { if (this.hideClearButtonWhenEmpty && Ext.isEmpty(this.textField.getValue())) { return false; } var clearButtonEl = this.clearButtonEl; if (this.hideClearButtonWhenMouseOut && !clearButtonEl.hasCls(this.clearButtonCls + '-mouse-over-button') && !clearButtonEl.hasCls(this.clearButtonCls + '-mouse-over-input')) { return false; } return true; }
And the last function that we called within the handleAfterRender
function is the
addEscListener
function. What we need to do is, if the configuration option clearOnEscape
is set to true
, add a key listener that will clear this field. Now, let us define this function:
addEscListener: function() { if (!this.clearOnEscape) { return; } this.textField.inputEl.on('keydown', function(e) { if (e.getKey() == Ext.EventObject.ESC) { if (this.textField.isExpanded) { return; } Ext.Function.defer(this.textField.setValue, 1,this.textField, ['']); e.stopEvent(); } }, this); }
The following screenshot is the output where we've used this plugin for textfield, textareafield, combobox, and the date field:
You can see that on hovering over the textfield component, the clear button is visible and clicking on this button will clear the respective field.