How to Build an Auto-Expanding Textarea jQuery Plugin, Part 3

Auto-Expanding TextareaIn part 1 we discovered how an auto-expanding textarea could be built and collated the requirements. In part 2 we determined how coders would initialize our component. It’s now time to get our hands dirty with some JavaScript.

We are building a jQuery plugin named TextAreaExpander. You can read about the intricacies of jQuery plugin development in this tutorial, but the barebones of our code is:


(function($) {

	// jQuery plugin definition
	$.fn.TextAreaExpander = function(minHeight, maxHeight) {

		// ... our code ...

		return this;
	};

})(jQuery);

The $.fn statement then declares our new jQuery plugin, TextAreaExpander, with the arguments minHeight and maxHeight. We could express these in a JSON object but it’s unlikely we’ll need further parameters so we’ll keep it simple.

Note that ‘this’ refers to the jQuery object; we return it to ensure other jQuery effects can be bound to the same DOM elements.

Initializing the Textarea

The following initialization code is added to our TextAreaExpander function:


// initialize
this.each(function() {

	// is a textarea?
	if (this.nodeName.toLowerCase() != "textarea") return;

	// set height restrictions
	var p = this.className.match(/expand(d+)-*(d+)*/i);
	this.expandMin = minHeight || (p ? parseInt('0'+p[1], 10) : 0);
	this.expandMax = maxHeight || (p ? parseInt('0'+p[2], 10) : 99999);

	// initial resize
	ResizeTextarea(this);

	// add events
	if (!this.Initialized) {
		this.Initialized = true;
		$(this).css("padding-top", 0).css("padding-bottom", 0);
		$(this).bind("keyup", ResizeTextarea).bind("focus", ResizeTextarea);
	}
});

return this;
};

This loops through all the jQuery-selected DOM nodes and runs an anonymous function. The value of ‘this’ within that function is an individual textarea node. The following initialization occurs:

  1. The first line ensures that only textareas have the auto-expanding effect applied.
  2. The next three lines extract the minimum and maximum height values. The arguments passed to the TextAreaExpander function are used by default. If none are specified, the textarea’s “expand” class HTML is analysed with a regular expression. If we still do not have values, 0 and 99999 are assumed (note that the textarea will always have a minimum height of 1 character, so a zero height is never applied). The values are stored as properties of the textarea node object — we can therefore examine them from any code.
  3. The following line calls a ResizeTextarea function and passes the textarea node. This will set the height to an appropriate size when auto-expanding is initialized.
  4. Finally, we reset any vertical padding and define “keyup” and “focus” events. The same ResizeTextarea function is called when the textarea receives focus and after text has been updated by the user. The ‘if’ condition around these events ensures that they can only be applied once to any textarea. This condition could have been applied to the whole of the initialization function, however, this code allows us to change the minimum and maximum heights at will.

Resizing the Textarea

We now need to define our ResizeTextarea function.

In part 1, we discussed browser differences and noted that IE and Opera should never set a textarea height of 0px. Therefore, we will assign a variable that returns false if IE or Opera is being used:


var hCheck = !($.browser.msie || $.browser.opera);

It’s dirty, but I’m afraid that we cannot rely on better methods such as object detection. I’m open to suggestions, though!

We can now code our ResizeTextarea function:


// resize a textarea
function ResizeTextarea(e) {

	// event or element?
	e = e.target || e;

	// find content length and box width
	var vlen = e.value.length, ewidth = e.offsetWidth;
	if (vlen != e.valLength || ewidth != e.boxWidth) {

		if (hCheck && (vlen < e.valLength || ewidth != e.boxWidth)) e.style.height = "0px";
		var h = Math.max(e.expandMin, Math.min(e.scrollHeight, e.expandMax));

		e.style.overflow = (e.scrollHeight > h ? "auto" : "hidden");
		e.style.height = h + "px";

		e.valLength = vlen;
		e.boxWidth = ewidth;
	}

	return true;
};

This function is passed the argument ‘e’. This is either a textarea node (during initialization) or an event object (when keyup or focus occurs).

  1. The first line changes ‘e’ to a textarea node object if an event was fired.
  2. The number of characters entered in the textarea is assigned to vlen. The pixel-width of the box is assigned to ewidth. If these values have not changed, we do not need to worry about resizing the box (the user could just be moving the cursor). vlen and ewidth are retained as properties of the textarea node object named valLength and boxWidth. These are set after the textarea is resized so resizing will always occur the first time ResizeTextarea is called.
  3. The next line resets the textarea height to 0px. This only occurs for non-IE/Opera browsers if content has been deleted or the box width has been changed.
  4. The textarea’s scrollHeight value is now assigned to variable ‘h’. Math.min and Math.max are used to ensure the value falls within the minimum and maximum pixel limits defined for this textarea.
  5. Before we change the textarea height, we change the CSS overflow property. The scrollbars will only be shown if the content height exceeds the textarea height.
  6. We can now modify the textarea height and update the values of valLength and boxWidth.
  7. Finally, the function returns true to ensure other textarea event handlers are not cancelled.

Our TextAreaExpander jQuery plugin is complete. However, we need to ensure that the effect is applied to all textarea tags with an HTML “expand” class. At the end of our file, we can add an event which initializes all appropriate textareas after the page has loaded:


// initialize all expanding textareas
jQuery(document).ready(function() {
	jQuery("textarea[class*=expand]").TextAreaExpander();
});

I hope you’ve found this series of tutorials helpful. Feel free to use the auto-expanding textarea plugin in your own projects.

Useful resources:

See also:

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • remi2611
  • http://www.cromwelldesigns.com Reaper2047

    I’d recommend using “keypress” instead of “keyup”.
    Using keypress lets it expand as lines are inserted if a key is not released(for example, if you hold down enter, it will pause updating the size until you release the key)

  • http://www.optimalworks.net/ Craig Buckler

    @Reaper2047
    That’s a good point, although it’s not something that occurs often in general typing.

  • Nikola

    Great plug-in although there are some minor issues in Safari (Windows) and Chrome. I guess setting the height to 0px is causing the jump. Also, hitting the space or delete key causes the TEXTAREA to continually re-size and grow…

  • Nikola

    With the code as it is and if your TEXTAREA’s are padded you’ll have to subtract the top and bottom padding from the height in Chrome and Safari (I’m using the Windows version of Safari).

    e.style.height = ($.browser.safari) ? h - padding + "px" : h + "px";

  • Anonymous

    If a key pressand hold, content will not be auto turn to another line.

  • http://www.optimalworks.net/ Craig Buckler

    @Nikola
    Are you using Safari 4? I get jumping/flickering if the cursor is at the end of the last line, I hit return, and the box expands. Bizarrely, no flickering occurs if the cursor is not on the last line?

    The JavaScript code sets the vertical padding to zero – it doesn’t matter what you define in the CSS. It’s a shame that needs to be done, but the browsers are just too inconsistent!

    @Anonymous
    Holding the key down will not expand/contract the box until you lift it…

    @Reaper2047
    I’ve just tried keypress and I wouldn’t recommend it. The keypress event occurs before the character is displayed in the textarea, so the scrollHeight is not necessarily accurate at that point.

  • http://www.brothercake.com/ brothercake

    In any case, it’s generally not a good idea to allow a key event which triggers scripted functionality to repeat natively – it tends to run a lot faster than event handling can keep up. So keyup was definitely the right choice, imo.

  • Nikola

    @Craig

    That’s pretty much it, yes. The same thing happens in Chrome as well. Instead of setting the height to zero I set it to scrollHeight/1.2. It’s an inelegant solution but it negates the jumping almost completely. I’m wondering if there’s another way to trigger an update to the scrollHeight without setting the height to less than the scrollHeight. That seems like it would be an ideal solution. I’ve seen a few other TEXTAREA resizers which work around the jump by cloning the TEXTAREA and updating / getting the appropriate dimensions from the hidden clone but I like this solution more… much less overhead and simple.

  • http://www.optimalworks.net/ Craig Buckler

    @Nikola
    Thanks for the feedback. I’ve just tried scrollHeight/1.2, but it didn’t solve the flickering for me? Also, if you hold down delete, it doesn’t appear to shrink the height correctly.

    A cloned textarea is an interesting solution, although I agree that it’s a lot of coding and effort for a fairly simple widget. Multiple textareas would also require multiple clones so performance could be affected.