textarea.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. // Copyright 2010 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A content-aware textarea control that grows and shrinks
  16. * automatically. This implementation extends {@link goog.ui.Control}.
  17. * This code is inspired by Dojo Dijit's Textarea implementation with
  18. * modifications to support native (when available) textarea resizing and
  19. * minHeight and maxHeight enforcement.
  20. *
  21. * @see ../demos/textarea.html
  22. */
  23. goog.provide('goog.ui.Textarea');
  24. goog.provide('goog.ui.Textarea.EventType');
  25. goog.require('goog.asserts');
  26. goog.require('goog.dom');
  27. goog.require('goog.dom.classlist');
  28. goog.require('goog.events.EventType');
  29. goog.require('goog.style');
  30. goog.require('goog.ui.Control');
  31. goog.require('goog.ui.TextareaRenderer');
  32. goog.require('goog.userAgent');
  33. /**
  34. * A textarea control to handle growing/shrinking with textarea.value.
  35. *
  36. * @param {string} content Text to set as the textarea's value.
  37. * @param {goog.ui.TextareaRenderer=} opt_renderer Renderer used to render or
  38. * decorate the textarea. Defaults to {@link goog.ui.TextareaRenderer}.
  39. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
  40. * document interaction.
  41. * @constructor
  42. * @extends {goog.ui.Control}
  43. */
  44. goog.ui.Textarea = function(content, opt_renderer, opt_domHelper) {
  45. goog.ui.Control.call(
  46. this, content, opt_renderer || goog.ui.TextareaRenderer.getInstance(),
  47. opt_domHelper);
  48. this.setHandleMouseEvents(false);
  49. this.setAllowTextSelection(true);
  50. this.hasUserInput_ = (content != '');
  51. if (!content) {
  52. this.setContentInternal('');
  53. }
  54. };
  55. goog.inherits(goog.ui.Textarea, goog.ui.Control);
  56. goog.tagUnsealableClass(goog.ui.Textarea);
  57. /**
  58. * Some UAs will shrink the textarea automatically, some won't.
  59. * @type {boolean}
  60. * @private
  61. */
  62. goog.ui.Textarea.NEEDS_HELP_SHRINKING_ =
  63. !(goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(11));
  64. /**
  65. * True if the resizing function is executing, false otherwise.
  66. * @type {boolean}
  67. * @private
  68. */
  69. goog.ui.Textarea.prototype.isResizing_ = false;
  70. /**
  71. * Represents if we have focus on the textarea element, used only
  72. * to render the placeholder if we don't have native placeholder
  73. * support.
  74. * @type {boolean}
  75. * @private
  76. */
  77. goog.ui.Textarea.prototype.hasFocusForPlaceholder_ = false;
  78. /**
  79. * @type {boolean}
  80. * @private
  81. */
  82. goog.ui.Textarea.prototype.hasUserInput_ = false;
  83. /**
  84. * The height of the textarea as last measured.
  85. * @type {number}
  86. * @private
  87. */
  88. goog.ui.Textarea.prototype.height_ = 0;
  89. /**
  90. * A maximum height for the textarea. When set to 0, the default, there is no
  91. * enforcement of this value during resize.
  92. * @type {number}
  93. * @private
  94. */
  95. goog.ui.Textarea.prototype.maxHeight_ = 0;
  96. /**
  97. * A minimum height for the textarea. When set to 0, the default, there is no
  98. * enforcement of this value during resize.
  99. * @type {number}
  100. * @private
  101. */
  102. goog.ui.Textarea.prototype.minHeight_ = 0;
  103. /**
  104. * Whether or not textarea rendering characteristics have been discovered.
  105. * Specifically we determine, at runtime:
  106. * If the padding and border box is included in offsetHeight.
  107. * @see {goog.ui.Textarea.prototype.needsPaddingBorderFix_}
  108. * If the padding and border box is included in scrollHeight.
  109. * @see {goog.ui.Textarea.prototype.scrollHeightIncludesPadding_} and
  110. * @see {goog.ui.Textarea.prototype.scrollHeightIncludesBorder_}
  111. * TODO(user): See if we can determine goog.ui.Textarea.NEEDS_HELP_SHRINKING_.
  112. * @type {boolean}
  113. * @private
  114. */
  115. goog.ui.Textarea.prototype.hasDiscoveredTextareaCharacteristics_ = false;
  116. /**
  117. * If a user agent doesn't correctly support the box-sizing:border-box CSS
  118. * value then we'll need to adjust our height calculations.
  119. * @see {goog.ui.Textarea.prototype.discoverTextareaCharacteristics_}
  120. * @type {boolean}
  121. * @private
  122. */
  123. goog.ui.Textarea.prototype.needsPaddingBorderFix_ = false;
  124. /**
  125. * Whether or not scrollHeight of a textarea includes the padding box.
  126. * @type {boolean}
  127. * @private
  128. */
  129. goog.ui.Textarea.prototype.scrollHeightIncludesPadding_ = false;
  130. /**
  131. * Whether or not scrollHeight of a textarea includes the border box.
  132. * @type {boolean}
  133. * @private
  134. */
  135. goog.ui.Textarea.prototype.scrollHeightIncludesBorder_ = false;
  136. /**
  137. * For storing the padding box size during enterDocument, to prevent possible
  138. * measurement differences that can happen after text zooming.
  139. * Note: runtime padding changes will cause problems with this.
  140. * @type {goog.math.Box}
  141. * @private
  142. */
  143. goog.ui.Textarea.prototype.paddingBox_;
  144. /**
  145. * For storing the border box size during enterDocument, to prevent possible
  146. * measurement differences that can happen after text zooming.
  147. * Note: runtime border width changes will cause problems with this.
  148. * @type {goog.math.Box}
  149. * @private
  150. */
  151. goog.ui.Textarea.prototype.borderBox_;
  152. /**
  153. * Default text content for the textarea when it is unchanged and unfocussed.
  154. * We use the placeholder attribute for all browsers that have support for
  155. * it (new in HTML5 for the following browsers:
  156. *
  157. * Internet Explorer 10.0
  158. * Firefox 4.0
  159. * Opera 11.6
  160. * Chrome 4.0
  161. * Safari 5.0
  162. *
  163. * For older browsers, we save the placeholderText_ and set it as the element's
  164. * value and add the TEXTAREA_PLACEHOLDER_CLASS to indicate that it's a
  165. * placeholder string.
  166. * @type {string}
  167. * @private
  168. */
  169. goog.ui.Textarea.prototype.placeholderText_ = '';
  170. /**
  171. * Constants for event names.
  172. * @enum {string}
  173. */
  174. goog.ui.Textarea.EventType = {
  175. RESIZE: 'resize'
  176. };
  177. /**
  178. * Sets the default text for the textarea.
  179. * @param {string} text The default text for the textarea.
  180. */
  181. goog.ui.Textarea.prototype.setPlaceholder = function(text) {
  182. this.placeholderText_ = text;
  183. if (this.getElement()) {
  184. this.restorePlaceholder_();
  185. }
  186. };
  187. /**
  188. * @return {number} The padding plus the border box height.
  189. * @private
  190. */
  191. goog.ui.Textarea.prototype.getPaddingBorderBoxHeight_ = function() {
  192. var paddingBorderBoxHeight = this.paddingBox_.top + this.paddingBox_.bottom +
  193. this.borderBox_.top + this.borderBox_.bottom;
  194. return paddingBorderBoxHeight;
  195. };
  196. /**
  197. * @return {number} The minHeight value.
  198. */
  199. goog.ui.Textarea.prototype.getMinHeight = function() {
  200. return this.minHeight_;
  201. };
  202. /**
  203. * @return {number} The minHeight value with a potential padding fix.
  204. * @private
  205. */
  206. goog.ui.Textarea.prototype.getMinHeight_ = function() {
  207. var minHeight = this.minHeight_;
  208. var textarea = this.getElement();
  209. if (minHeight && textarea && this.needsPaddingBorderFix_) {
  210. minHeight -= this.getPaddingBorderBoxHeight_();
  211. }
  212. return minHeight;
  213. };
  214. /**
  215. * Sets a minimum height for the textarea, and calls resize if rendered.
  216. * @param {number} height New minHeight value.
  217. */
  218. goog.ui.Textarea.prototype.setMinHeight = function(height) {
  219. this.minHeight_ = height;
  220. this.resize();
  221. };
  222. /**
  223. * @return {number} The maxHeight value.
  224. */
  225. goog.ui.Textarea.prototype.getMaxHeight = function() {
  226. return this.maxHeight_;
  227. };
  228. /**
  229. * @return {number} The maxHeight value with a potential padding fix.
  230. * @private
  231. */
  232. goog.ui.Textarea.prototype.getMaxHeight_ = function() {
  233. var maxHeight = this.maxHeight_;
  234. var textarea = this.getElement();
  235. if (maxHeight && textarea && this.needsPaddingBorderFix_) {
  236. maxHeight -= this.getPaddingBorderBoxHeight_();
  237. }
  238. return maxHeight;
  239. };
  240. /**
  241. * Sets a maximum height for the textarea, and calls resize if rendered.
  242. * @param {number} height New maxHeight value.
  243. */
  244. goog.ui.Textarea.prototype.setMaxHeight = function(height) {
  245. this.maxHeight_ = height;
  246. this.resize();
  247. };
  248. /**
  249. * Sets the textarea's value.
  250. * @param {*} value The value property for the textarea, will be cast to a
  251. * string by the browser when setting textarea.value.
  252. */
  253. goog.ui.Textarea.prototype.setValue = function(value) {
  254. this.setContent(String(value));
  255. };
  256. /**
  257. * Gets the textarea's value.
  258. * @return {string} value The value of the textarea.
  259. */
  260. goog.ui.Textarea.prototype.getValue = function() {
  261. // We potentially have the placeholder stored in the value.
  262. // If a client of this class sets this.getElement().value directly
  263. // we don't set the this.hasUserInput_ boolean. Thus, we need to
  264. // explicitly check if the value != the placeholder text. This has
  265. // the unfortunate edge case of:
  266. // If the client sets this.getElement().value to the placeholder
  267. // text, we'll return the empty string.
  268. // The normal use case shouldn't be an issue, however, since the
  269. // default placeholderText is the empty string. Also, if the end user
  270. // inputs text, then this.hasUserInput_ will always be true.
  271. if (this.getElement().value != this.placeholderText_ ||
  272. this.supportsNativePlaceholder_() || this.hasUserInput_) {
  273. // We don't do anything fancy here.
  274. return this.getElement().value;
  275. }
  276. return '';
  277. };
  278. /** @override */
  279. goog.ui.Textarea.prototype.setContent = function(content) {
  280. goog.ui.Textarea.superClass_.setContent.call(this, content);
  281. this.hasUserInput_ = (content != '');
  282. this.resize();
  283. };
  284. /** @override **/
  285. goog.ui.Textarea.prototype.setEnabled = function(enable) {
  286. goog.ui.Textarea.superClass_.setEnabled.call(this, enable);
  287. this.getElement().disabled = !enable;
  288. };
  289. /**
  290. * Resizes the textarea vertically.
  291. */
  292. goog.ui.Textarea.prototype.resize = function() {
  293. if (this.getElement()) {
  294. this.grow_();
  295. }
  296. };
  297. /**
  298. * @return {boolean} True if the element supports the placeholder attribute.
  299. * @private
  300. */
  301. goog.ui.Textarea.prototype.supportsNativePlaceholder_ = function() {
  302. goog.asserts.assert(this.getElement());
  303. return 'placeholder' in this.getElement();
  304. };
  305. /**
  306. * Sets the value of the textarea element to the default text.
  307. * @private
  308. */
  309. goog.ui.Textarea.prototype.restorePlaceholder_ = function() {
  310. if (!this.placeholderText_) {
  311. // Return early if there is no placeholder to mess with.
  312. return;
  313. }
  314. // Check again in case something changed since this was scheduled.
  315. // We check that the element is still there since this is called by a timer
  316. // and the dispose method may have been called prior to this.
  317. if (this.supportsNativePlaceholder_()) {
  318. this.getElement().placeholder = this.placeholderText_;
  319. } else if (
  320. this.getElement() && !this.hasUserInput_ &&
  321. !this.hasFocusForPlaceholder_) {
  322. // We only want to set the value + placeholder CSS if we actually have
  323. // some placeholder text to show.
  324. goog.dom.classlist.add(
  325. goog.asserts.assert(this.getElement()),
  326. goog.ui.Textarea.TEXTAREA_PLACEHOLDER_CLASS);
  327. this.getElement().value = this.placeholderText_;
  328. }
  329. };
  330. /** @override **/
  331. goog.ui.Textarea.prototype.enterDocument = function() {
  332. goog.ui.Textarea.base(this, 'enterDocument');
  333. var textarea = this.getElement();
  334. // Eliminates the vertical scrollbar and changes the box-sizing mode for the
  335. // textarea to the border-box (aka quirksmode) paradigm.
  336. goog.style.setStyle(textarea, {
  337. 'overflowY': 'hidden',
  338. 'overflowX': 'auto',
  339. 'boxSizing': 'border-box',
  340. 'MsBoxSizing': 'border-box',
  341. 'WebkitBoxSizing': 'border-box',
  342. 'MozBoxSizing': 'border-box'
  343. });
  344. this.paddingBox_ = goog.style.getPaddingBox(textarea);
  345. this.borderBox_ = goog.style.getBorderBox(textarea);
  346. this.getHandler()
  347. .listen(textarea, goog.events.EventType.SCROLL, this.grow_)
  348. .listen(textarea, goog.events.EventType.FOCUS, this.grow_)
  349. .listen(textarea, goog.events.EventType.KEYUP, this.grow_)
  350. .listen(textarea, goog.events.EventType.MOUSEUP, this.mouseUpListener_)
  351. .listen(textarea, goog.events.EventType.BLUR, this.blur_);
  352. this.restorePlaceholder_();
  353. this.resize();
  354. };
  355. /**
  356. * Gets the textarea's content height + padding height + border height.
  357. * This is done by getting the scrollHeight and adjusting from there.
  358. * In the end this result is what we want the new offsetHeight to equal.
  359. * @return {number} The height of the textarea.
  360. * @private
  361. */
  362. goog.ui.Textarea.prototype.getHeight_ = function() {
  363. this.discoverTextareaCharacteristics_();
  364. var textarea = this.getElement();
  365. // Because enterDocument can be called even when the component is rendered
  366. // without being in a document, we may not have cached the correct paddingBox
  367. // data on render(). We try to make up for this here.
  368. if (isNaN(this.paddingBox_.top)) {
  369. this.paddingBox_ = goog.style.getPaddingBox(textarea);
  370. this.borderBox_ = goog.style.getBorderBox(textarea);
  371. }
  372. // Accounts for a possible (though unlikely) horizontal scrollbar.
  373. var height =
  374. this.getElement().scrollHeight + this.getHorizontalScrollBarHeight_();
  375. if (this.needsPaddingBorderFix_) {
  376. height -= this.getPaddingBorderBoxHeight_();
  377. } else {
  378. if (!this.scrollHeightIncludesPadding_) {
  379. var paddingBox = this.paddingBox_;
  380. var paddingBoxHeight = paddingBox.top + paddingBox.bottom;
  381. height += paddingBoxHeight;
  382. }
  383. if (!this.scrollHeightIncludesBorder_) {
  384. var borderBox = goog.style.getBorderBox(textarea);
  385. var borderBoxHeight = borderBox.top + borderBox.bottom;
  386. height += borderBoxHeight;
  387. }
  388. }
  389. return height;
  390. };
  391. /**
  392. * Sets the textarea's height.
  393. * @param {number} height The height to set.
  394. * @private
  395. */
  396. goog.ui.Textarea.prototype.setHeight_ = function(height) {
  397. if (this.height_ != height) {
  398. this.height_ = height;
  399. this.getElement().style.height = height + 'px';
  400. }
  401. };
  402. /**
  403. * Sets the textarea's rows attribute to be the number of newlines + 1.
  404. * This is necessary when the textarea is hidden, in which case scrollHeight
  405. * is not available.
  406. * @private
  407. */
  408. goog.ui.Textarea.prototype.setHeightToEstimate_ = function() {
  409. var textarea = this.getElement();
  410. textarea.style.height = 'auto';
  411. var newlines = textarea.value.match(/\n/g) || [];
  412. textarea.rows = newlines.length + 1;
  413. this.height_ = 0;
  414. };
  415. /**
  416. * Gets the the height of (possibly present) horizontal scrollbar.
  417. * @return {number} The height of the horizontal scrollbar.
  418. * @private
  419. */
  420. goog.ui.Textarea.prototype.getHorizontalScrollBarHeight_ = function() {
  421. var textarea = /** @type {!HTMLElement} */ (this.getElement());
  422. var height = textarea.offsetHeight - textarea.clientHeight;
  423. if (!this.scrollHeightIncludesPadding_) {
  424. var paddingBox = this.paddingBox_;
  425. var paddingBoxHeight = paddingBox.top + paddingBox.bottom;
  426. height -= paddingBoxHeight;
  427. }
  428. if (!this.scrollHeightIncludesBorder_) {
  429. var borderBox = goog.style.getBorderBox(textarea);
  430. var borderBoxHeight = borderBox.top + borderBox.bottom;
  431. height -= borderBoxHeight;
  432. }
  433. // Prevent negative number results, which sometimes show up.
  434. return height > 0 ? height : 0;
  435. };
  436. /**
  437. * In order to assess the correct height for a textarea, we need to know
  438. * whether the scrollHeight (the full height of the text) property includes
  439. * the values for padding and borders. We can also test whether the
  440. * box-sizing: border-box setting is working and then tweak accordingly.
  441. * Instead of hardcoding a list of currently known behaviors and testing
  442. * for quirksmode, we do a runtime check out of the flow. The performance
  443. * impact should be very small.
  444. * @private
  445. */
  446. goog.ui.Textarea.prototype.discoverTextareaCharacteristics_ = function() {
  447. if (!this.hasDiscoveredTextareaCharacteristics_) {
  448. var textarea =
  449. /** @type {!HTMLElement} */ (this.getElement().cloneNode(false));
  450. // We need to overwrite/write box model specific styles that might
  451. // affect height.
  452. goog.style.setStyle(textarea, {
  453. 'position': 'absolute',
  454. 'height': 'auto',
  455. 'top': '-9999px',
  456. 'margin': '0',
  457. 'padding': '1px',
  458. 'border': '1px solid #000',
  459. 'overflow': 'hidden'
  460. });
  461. goog.dom.appendChild(this.getDomHelper().getDocument().body, textarea);
  462. var initialScrollHeight = textarea.scrollHeight;
  463. textarea.style.padding = '10px';
  464. var paddingScrollHeight = textarea.scrollHeight;
  465. this.scrollHeightIncludesPadding_ =
  466. paddingScrollHeight > initialScrollHeight;
  467. initialScrollHeight = paddingScrollHeight;
  468. textarea.style.borderWidth = '10px';
  469. var borderScrollHeight = textarea.scrollHeight;
  470. this.scrollHeightIncludesBorder_ = borderScrollHeight > initialScrollHeight;
  471. // Tests if border-box sizing is working or not.
  472. textarea.style.height = '100px';
  473. var offsetHeightAtHeight100 = textarea.offsetHeight;
  474. if (offsetHeightAtHeight100 != 100) {
  475. this.needsPaddingBorderFix_ = true;
  476. }
  477. goog.dom.removeNode(textarea);
  478. this.hasDiscoveredTextareaCharacteristics_ = true;
  479. }
  480. };
  481. /**
  482. * The CSS class name to add to the input when the user has not entered a
  483. * value.
  484. */
  485. goog.ui.Textarea.TEXTAREA_PLACEHOLDER_CLASS =
  486. goog.getCssName('textarea-placeholder-input');
  487. /**
  488. * Called when the element goes out of focus.
  489. * @param {goog.events.Event=} opt_e The browser event.
  490. * @private
  491. */
  492. goog.ui.Textarea.prototype.blur_ = function(opt_e) {
  493. if (!this.supportsNativePlaceholder_()) {
  494. this.hasFocusForPlaceholder_ = false;
  495. if (this.getElement().value == '') {
  496. // Only transition to the default text if we have
  497. // no user input.
  498. this.hasUserInput_ = false;
  499. this.restorePlaceholder_();
  500. }
  501. }
  502. };
  503. /**
  504. * Resizes the textarea to grow/shrink to match its contents.
  505. * @param {goog.events.Event=} opt_e The browser event.
  506. * @private
  507. */
  508. goog.ui.Textarea.prototype.grow_ = function(opt_e) {
  509. if (this.isResizing_) {
  510. return;
  511. }
  512. var textarea = /** @type {!HTMLElement} */ (this.getElement());
  513. // If the element is getting focus and we don't support placeholders
  514. // natively, then remove the placeholder class.
  515. if (!this.supportsNativePlaceholder_() && opt_e &&
  516. opt_e.type == goog.events.EventType.FOCUS) {
  517. // We must have a textarea element, since we're growing it.
  518. // Remove the placeholder CSS + set the value to empty if we're currently
  519. // showing the placeholderText_ value if this is the first time we're
  520. // getting focus.
  521. if (textarea.value == this.placeholderText_ && this.placeholderText_ &&
  522. !this.hasFocusForPlaceholder_) {
  523. goog.dom.classlist.remove(
  524. textarea, goog.ui.Textarea.TEXTAREA_PLACEHOLDER_CLASS);
  525. textarea.value = '';
  526. }
  527. this.hasFocusForPlaceholder_ = true;
  528. this.hasUserInput_ = (textarea.value != '');
  529. }
  530. var shouldCallShrink = false;
  531. this.isResizing_ = true;
  532. var oldHeight = this.height_;
  533. if (textarea.scrollHeight) {
  534. var setMinHeight = false;
  535. var setMaxHeight = false;
  536. var newHeight = this.getHeight_();
  537. var currentHeight = textarea.offsetHeight;
  538. var minHeight = this.getMinHeight_();
  539. var maxHeight = this.getMaxHeight_();
  540. if (minHeight && newHeight < minHeight) {
  541. this.setHeight_(minHeight);
  542. setMinHeight = true;
  543. } else if (maxHeight && newHeight > maxHeight) {
  544. this.setHeight_(maxHeight);
  545. // If the content is greater than the height, we'll want the vertical
  546. // scrollbar back.
  547. textarea.style.overflowY = '';
  548. setMaxHeight = true;
  549. } else if (currentHeight != newHeight) {
  550. this.setHeight_(newHeight);
  551. // Makes sure that height_ is at least set.
  552. } else if (!this.height_) {
  553. this.height_ = newHeight;
  554. }
  555. if (!setMinHeight && !setMaxHeight &&
  556. goog.ui.Textarea.NEEDS_HELP_SHRINKING_) {
  557. shouldCallShrink = true;
  558. }
  559. } else {
  560. this.setHeightToEstimate_();
  561. }
  562. this.isResizing_ = false;
  563. if (shouldCallShrink) {
  564. this.shrink_();
  565. }
  566. if (oldHeight != this.height_) {
  567. this.dispatchEvent(goog.ui.Textarea.EventType.RESIZE);
  568. }
  569. };
  570. /**
  571. * Resizes the textarea to shrink to fit its contents. The way this works is
  572. * by increasing the padding of the textarea by 1px (it's important here that
  573. * we're in box-sizing: border-box mode). If the size of the textarea grows,
  574. * then the box is filled up to the padding box with text.
  575. * If it doesn't change, then we can shrink.
  576. * @private
  577. */
  578. goog.ui.Textarea.prototype.shrink_ = function() {
  579. var textarea = this.getElement();
  580. if (!this.isResizing_) {
  581. this.isResizing_ = true;
  582. var scrollHeight = textarea.scrollHeight;
  583. if (!scrollHeight) {
  584. this.setHeightToEstimate_();
  585. } else {
  586. var currentHeight = this.getHeight_();
  587. var minHeight = this.getMinHeight_();
  588. if (!(minHeight && currentHeight <= minHeight)) {
  589. // Nudge the padding by 1px.
  590. var paddingBox = this.paddingBox_;
  591. textarea.style.paddingBottom = paddingBox.bottom + 1 + 'px';
  592. var heightAfterNudge = this.getHeight_();
  593. // If the one px of padding had no effect, then we can shrink.
  594. if (heightAfterNudge == currentHeight) {
  595. textarea.style.paddingBottom =
  596. paddingBox.bottom + scrollHeight + 'px';
  597. textarea.scrollTop = 0;
  598. var shrinkToHeight = this.getHeight_() - scrollHeight;
  599. if (shrinkToHeight >= minHeight) {
  600. this.setHeight_(shrinkToHeight);
  601. } else {
  602. this.setHeight_(minHeight);
  603. }
  604. }
  605. textarea.style.paddingBottom = paddingBox.bottom + 'px';
  606. }
  607. }
  608. this.isResizing_ = false;
  609. }
  610. };
  611. /**
  612. * We use this listener to check if the textarea has been natively resized
  613. * and if so we reset minHeight so that we don't ever shrink smaller than
  614. * the user's manually set height. Note that we cannot check size on mousedown
  615. * and then just compare here because we cannot capture mousedown on
  616. * the textarea resizer, while mouseup fires reliably.
  617. * @param {goog.events.BrowserEvent} e The mousedown event.
  618. * @private
  619. */
  620. goog.ui.Textarea.prototype.mouseUpListener_ = function(e) {
  621. var textarea = /** @type {!HTMLElement} */ (this.getElement());
  622. var height = textarea.offsetHeight;
  623. // This solves for when the MSIE DropShadow filter is enabled,
  624. // as it affects the offsetHeight value, even with MsBoxSizing:border-box.
  625. if (textarea['filters'] && textarea['filters'].length) {
  626. var dropShadow =
  627. textarea['filters']['item']('DXImageTransform.Microsoft.DropShadow');
  628. if (dropShadow) {
  629. height -= dropShadow['offX'];
  630. }
  631. }
  632. if (height != this.height_) {
  633. this.minHeight_ = height;
  634. this.height_ = height;
  635. }
  636. };