ClearButton.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. (function() {
  2. /**
  3. * @class Ext.ux.form.field.ClearButton
  4. *
  5. * Plugin for text components that shows a "clear" button over the text field.
  6. * When the button is clicked the text field is set empty.
  7. * Icon image and positioning can be controlled using CSS.
  8. * Works with Ext.form.field.Text, Ext.form.field.TextArea, Ext.form.field.ComboBox and Ext.form.field.Date.
  9. *
  10. * Plugin alias is 'clearbutton' (use "plugins: 'clearbutton'" in GridPanel config).
  11. *
  12. * @author <a href="mailto:stephen.friedrich@fortis-it.de">Stephen Friedrich</a>
  13. * @author <a href="mailto:fabian.urban@fortis-it.de">Fabian Urban</a>
  14. *
  15. * @copyright (c) 2011 Fortis IT Services GmbH
  16. * @license Ext.ux.form.field.ClearButton is released under the
  17. * <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>.
  18. *
  19. */
  20. Ext.define('Ext.ux.form.field.ClearButton', {
  21. alias: 'plugin.clearbutton',
  22. /**
  23. * @cfg {Boolean} Hide the clear button when the field is empty (default: true).
  24. */
  25. hideClearButtonWhenEmpty: true,
  26. /**
  27. * @cfg {Boolean} Hide the clear button until the mouse is over the field (default: true).
  28. */
  29. hideClearButtonWhenMouseOut: true,
  30. /**
  31. * @cfg {Boolean} When the clear buttons is hidden/shown, this will animate the button to its new state (using opacity) (default: true).
  32. */
  33. animateClearButton: true,
  34. /**
  35. * @cfg {Boolean} Empty the text field when ESC is pressed while the text field is focused.
  36. */
  37. clearOnEscape: true,
  38. /**
  39. * @cfg {String} CSS class used for the button div.
  40. * Also used as a prefix for other classes (suffixes: '-mouse-over-input', '-mouse-over-button', '-mouse-down', '-on', '-off')
  41. */
  42. clearButtonCls: 'ext-ux-clearbutton',
  43. /**
  44. * The text field (or text area, combo box, date field) that we are attached to
  45. */
  46. textField: null,
  47. /**
  48. * Will be set to true if animateClearButton is true and the browser supports CSS 3 transitions
  49. * @private
  50. */
  51. animateWithCss3: false,
  52. /////////////////////////////////////////////////////////////////////////////////////////////////////
  53. //
  54. // Set up and tear down
  55. //
  56. /////////////////////////////////////////////////////////////////////////////////////////////////////
  57. constructor: function(cfg) {
  58. Ext.apply(this, cfg);
  59. this.callParent(arguments);
  60. },
  61. /**
  62. * Called by plug-in system to initialize the plugin for a specific text field (or text area, combo box, date field).
  63. * Most all the setup is delayed until the component is rendered.
  64. */
  65. init: function(textField) {
  66. this.textField = textField;
  67. if (!textField.rendered) {
  68. textField.on('afterrender', this.handleAfterRender, this);
  69. }
  70. else {
  71. // probably an existing input element transformed to extjs field
  72. this.handleAfterRender();
  73. }
  74. },
  75. /**
  76. * After the field has been rendered sets up the plugin (create the Element for the clear button, attach listeners).
  77. * @private
  78. */
  79. handleAfterRender: function(textField) {
  80. this.isTextArea = (this.textField.inputEl.dom.type.toLowerCase() == 'textarea');
  81. this.createClearButtonEl();
  82. this.addListeners();
  83. this.repositionClearButton();
  84. this.updateClearButtonVisibility();
  85. this.addEscListener();
  86. },
  87. /**
  88. * Creates the Element and DOM for the clear button
  89. */
  90. createClearButtonEl: function() {
  91. var animateWithClass = this.animateClearButton && this.animateWithCss3;
  92. this.clearButtonEl = this.textField.bodyEl.createChild({
  93. tag: 'div',
  94. cls: this.clearButtonCls
  95. });
  96. if(this.animateClearButton) {
  97. this.animateWithCss3 = this.supportsCssTransition(this.clearButtonEl);
  98. }
  99. if(this.animateWithCss3) {
  100. this.clearButtonEl.addCls(this.clearButtonCls + '-off');
  101. }
  102. else {
  103. this.clearButtonEl.setStyle('visibility', 'hidden');
  104. }
  105. },
  106. /**
  107. * Returns true iff the browser supports CSS 3 transitions
  108. * @param el an element that is checked for support of the "transition" CSS property (considering any
  109. * vendor prefixes)
  110. */
  111. supportsCssTransition: function(el) {
  112. var styles = ['transitionProperty', 'WebkitTransitionProperty', 'MozTransitionProperty',
  113. 'OTransitionProperty', 'msTransitionProperty', 'KhtmlTransitionProperty'];
  114. var style = el.dom.style;
  115. for(var i = 0, length = styles.length; i < length; ++i) {
  116. if(style[styles[i]] !== 'undefined') {
  117. // Supported property will result in empty string
  118. return true;
  119. }
  120. }
  121. return false;
  122. },
  123. /**
  124. * If config option "clearOnEscape" is true, then add a key listener that will clear this field
  125. */
  126. addEscListener: function() {
  127. if (!this.clearOnEscape) {
  128. return;
  129. }
  130. // Using a KeyMap did not work: ESC is swallowed by combo box and date field before it reaches our own KeyMap
  131. this.textField.inputEl.on('keydown',
  132. function(e) {
  133. if (e.getKey() == Ext.EventObject.ESC) {
  134. if (this.textField.isExpanded) {
  135. // Let combo box or date field first remove the popup
  136. return;
  137. }
  138. // No idea why the defer is necessary, but otherwise the call to setValue('') is ignored
  139. Ext.Function.defer(this.textField.setValue, 1, this.textField, ['']);
  140. e.stopEvent();
  141. }
  142. },
  143. this);
  144. },
  145. /**
  146. * Adds listeners to the field, its input element and the clear button to handle resizing, mouse over/out events, click events etc.
  147. */
  148. addListeners: function() {
  149. // listeners on input element (DOM/El level)
  150. var textField = this.textField;
  151. var bodyEl = textField.bodyEl;
  152. bodyEl.on('mouseover', this.handleMouseOverInputField, this);
  153. bodyEl.on('mouseout', this.handleMouseOutOfInputField, this);
  154. // listeners on text field (component level)
  155. textField.on('destroy', this.handleDestroy, this);
  156. textField.on('resize', this.repositionClearButton, this);
  157. textField.on('change', function() {
  158. this.repositionClearButton();
  159. this.updateClearButtonVisibility();
  160. }, this);
  161. // listeners on clear button (DOM/El level)
  162. var clearButtonEl = this.clearButtonEl;
  163. clearButtonEl.on('mouseover', this.handleMouseOverClearButton, this);
  164. clearButtonEl.on('mouseout', this.handleMouseOutOfClearButton, this);
  165. clearButtonEl.on('mousedown', this.handleMouseDownOnClearButton, this);
  166. clearButtonEl.on('mouseup', this.handleMouseUpOnClearButton, this);
  167. clearButtonEl.on('click', this.handleMouseClickOnClearButton, this);
  168. },
  169. /**
  170. * When the field is destroyed, we also need to destroy the clear button Element to prevent memory leaks.
  171. */
  172. handleDestroy: function() {
  173. this.clearButtonEl.destroy();
  174. },
  175. /////////////////////////////////////////////////////////////////////////////////////////////////////
  176. //
  177. // Mouse event handlers
  178. //
  179. /////////////////////////////////////////////////////////////////////////////////////////////////////
  180. /**
  181. * Tada - the real action: If user left clicked on the clear button, then empty the field
  182. */
  183. handleMouseClickOnClearButton: function(event, htmlElement, object) {
  184. if (!this.isLeftButton(event)) {
  185. return;
  186. }
  187. this.textField.setValue('');
  188. this.textField.focus();
  189. },
  190. handleMouseOverInputField: function(event, htmlElement, object) {
  191. this.clearButtonEl.addCls(this.clearButtonCls + '-mouse-over-input');
  192. if (event.getRelatedTarget() == this.clearButtonEl.dom) {
  193. // Moused moved to clear button and will generate another mouse event there.
  194. // Handle it here to avoid duplicate updates (else animation will break)
  195. this.clearButtonEl.removeCls(this.clearButtonCls + '-mouse-over-button');
  196. this.clearButtonEl.removeCls(this.clearButtonCls + '-mouse-down');
  197. }
  198. this.updateClearButtonVisibility();
  199. },
  200. handleMouseOutOfInputField: function(event, htmlElement, object) {
  201. this.clearButtonEl.removeCls(this.clearButtonCls + '-mouse-over-input');
  202. if (event.getRelatedTarget() == this.clearButtonEl.dom) {
  203. // Moused moved from clear button and will generate another mouse event there.
  204. // Handle it here to avoid duplicate updates (else animation will break)
  205. this.clearButtonEl.addCls(this.clearButtonCls + '-mouse-over-button');
  206. }
  207. this.updateClearButtonVisibility();
  208. },
  209. handleMouseOverClearButton: function(event, htmlElement, object) {
  210. event.stopEvent();
  211. if (this.textField.bodyEl.contains(event.getRelatedTarget())) {
  212. // has been handled in handleMouseOutOfInputField() to prevent double update
  213. return;
  214. }
  215. this.clearButtonEl.addCls(this.clearButtonCls + '-mouse-over-button');
  216. this.updateClearButtonVisibility();
  217. },
  218. handleMouseOutOfClearButton: function(event, htmlElement, object) {
  219. event.stopEvent();
  220. if (this.textField.bodyEl.contains(event.getRelatedTarget())) {
  221. // will be handled in handleMouseOverInputField() to prevent double update
  222. return;
  223. }
  224. this.clearButtonEl.removeCls(this.clearButtonCls + '-mouse-over-button');
  225. this.clearButtonEl.removeCls(this.clearButtonCls + '-mouse-down');
  226. this.updateClearButtonVisibility();
  227. },
  228. handleMouseDownOnClearButton: function(event, htmlElement, object) {
  229. if (!this.isLeftButton(event)) {
  230. return;
  231. }
  232. this.clearButtonEl.addCls(this.clearButtonCls + '-mouse-down');
  233. },
  234. handleMouseUpOnClearButton: function(event, htmlElement, object) {
  235. if (!this.isLeftButton(event)) {
  236. return;
  237. }
  238. this.clearButtonEl.removeCls(this.clearButtonCls + '-mouse-down');
  239. },
  240. /////////////////////////////////////////////////////////////////////////////////////////////////////
  241. //
  242. // Utility methods
  243. //
  244. /////////////////////////////////////////////////////////////////////////////////////////////////////
  245. /**
  246. * Repositions the clear button element based on the textfield.inputEl element
  247. * @private
  248. */
  249. repositionClearButton: function() {
  250. var clearButtonEl = this.clearButtonEl;
  251. if (!clearButtonEl) {
  252. return;
  253. }
  254. var clearButtonPosition = this.calculateClearButtonPosition(this.textField);
  255. clearButtonEl.dom.style.right = clearButtonPosition.right + 'px';
  256. clearButtonEl.dom.style.top = clearButtonPosition.top + 'px';
  257. },
  258. /**
  259. * Calculates the position of the clear button based on the textfield.inputEl element
  260. * @private
  261. */
  262. calculateClearButtonPosition: function(textField) {
  263. var positions = textField.inputEl.getBox(true, true);
  264. var top = positions.y;
  265. var right = positions.x;
  266. if (this.fieldHasScrollBar()) {
  267. right += Ext.getScrollBarWidth();
  268. }
  269. if (this.textField.triggerWrap) {
  270. right += this.textField.getTriggerWidth();
  271. }
  272. return {
  273. right: right,
  274. top: top
  275. };
  276. },
  277. /**
  278. * Checks if the field we are attached to currently has a scrollbar
  279. */
  280. fieldHasScrollBar: function() {
  281. if (!this.isTextArea) {
  282. return false;
  283. }
  284. var inputEl = this.textField.inputEl;
  285. var overflowY = inputEl.getStyle('overflow-y');
  286. if (overflowY == 'hidden' || overflowY == 'visible') {
  287. return false;
  288. }
  289. if (overflowY == 'scroll') {
  290. return true;
  291. }
  292. //noinspection RedundantIfStatementJS
  293. if (inputEl.dom.scrollHeight <= inputEl.dom.clientHeight) {
  294. return false;
  295. }
  296. return true;
  297. },
  298. /**
  299. * Small wrapper around clearButtonEl.isVisible() to handle setVisible animation that may still be in progress.
  300. */
  301. isButtonCurrentlyVisible: function() {
  302. if (this.animateClearButton && this.animateWithCss3) {
  303. return this.clearButtonEl.hasCls(this.clearButtonCls + '-on');
  304. }
  305. // This should not be necessary (see Element.setVisible/isVisible), but else there is confusion about visibility
  306. // when moving the mouse out and _quickly_ over then input again.
  307. var cachedVisible = Ext.core.Element.data(this.clearButtonEl.dom, 'isVisible');
  308. if (typeof(cachedVisible) == 'boolean') {
  309. return cachedVisible;
  310. }
  311. return this.clearButtonEl.isVisible();
  312. },
  313. /**
  314. * Checks config options and current mouse status to determine if the clear button should be visible.
  315. */
  316. shouldButtonBeVisible: function() {
  317. if (this.hideClearButtonWhenEmpty && Ext.isEmpty(this.textField.getValue())) {
  318. return false;
  319. }
  320. var clearButtonEl = this.clearButtonEl;
  321. //noinspection RedundantIfStatementJS
  322. if (this.hideClearButtonWhenMouseOut
  323. && !clearButtonEl.hasCls(this.clearButtonCls + '-mouse-over-button')
  324. && !clearButtonEl.hasCls(this.clearButtonCls + '-mouse-over-input')) {
  325. return false;
  326. }
  327. return true;
  328. },
  329. /**
  330. * Called after any event that may influence the clear button visibility.
  331. */
  332. updateClearButtonVisibility: function() {
  333. var oldVisible = this.isButtonCurrentlyVisible();
  334. var newVisible = this.shouldButtonBeVisible();
  335. var clearButtonEl = this.clearButtonEl;
  336. if (oldVisible != newVisible) {
  337. if(this.animateClearButton && this.animateWithCss3) {
  338. this.clearButtonEl.removeCls(this.clearButtonCls + (oldVisible ? '-on' : '-off'));
  339. clearButtonEl.addCls(this.clearButtonCls + (newVisible ? '-on' : '-off'));
  340. }
  341. else {
  342. clearButtonEl.stopAnimation();
  343. clearButtonEl.setVisible(newVisible, this.animateClearButton);
  344. }
  345. // Set background-color of clearButton to same as field's background-color (for those browsers/cases
  346. // where the padding-right (see below) does not work)
  347. clearButtonEl.setStyle('background-color', this.textField.inputEl.getStyle('background-color'));
  348. // Adjust padding-right of the input tag to make room for the button
  349. // IE (up to v9) just ignores this and Gecko handles padding incorrectly with textarea scrollbars
  350. if (!(this.isTextArea && Ext.isGecko) && !Ext.isIE) {
  351. // See https://bugzilla.mozilla.org/show_bug.cgi?id=157846
  352. var deltaPaddingRight = clearButtonEl.getWidth() - this.clearButtonEl.getMargin('l');
  353. var currentPaddingRight = this.textField.inputEl.getPadding('r');
  354. var factor = (newVisible ? +1 : -1);
  355. this.textField.inputEl.dom.style.paddingRight = (currentPaddingRight + factor * deltaPaddingRight) + 'px';
  356. }
  357. }
  358. },
  359. isLeftButton: function(event) {
  360. return event.button === 0;
  361. }
  362. });
  363. })();