Automatically expand or contract a textarea with Javascript Prototype

a fresh how-to guide with the help of the powerful Javascript framework named Prototype

1. Demo: "Javascript tutorial auto expand and contract a form's textarea"

Tutorial on how to automatically expand or contract a form's textarea with Javascript Prototype

2. Introduction

2.1. What is Prototype? Toggle View

2.2. You will need:

  1. to download the Prototype Javascript framework

3. Javascript for expanding and contracting a form's textarea

Save as autoExpandContract.js and include that file in your document just below the call to your prototype.js.
document.observe("dom:loaded", function() {

	// we get all elements with a class set to 'expand'.
	var elements = $$('.expand');
	// size() uses the array's native length property
	// if the array has more than 0 items, we do the loop
	if (elements.size() > 0) {
		for (var index = 0, length = elements.size(); index < length; ++index)	{
			autoExpandContract(elements[index]);
		}
	}
	
	
	function getStyleFromCSS(el, style) {
	
		// get styles from our CSS
		var value = $(el).getStyle(style);
		
		// if styles are not defined in our CSS
		if(!value) {
		
			// for other browsers. Actually this equals 'window'. Use that if Opera fails on you.
			if(document.defaultView) {
				//	getComputedStyle() requires two parameters. 
				//		The first is a reference to the element. 
				//		The second is either the name of a pseudo element ('before', 'after', 'first-line'), 
				//		or null just for the element itself
				// getPropertyValue() 
				//		returns the value of the property if it has been set. 
				// 		Returns an empty string if the property has not been set.
				value = document.defaultView.getComputedStyle(el, null).getPropertyValue(style);
				
		   // for IE
			} else if(el.currentStyle) {
				//	As well as being an actual usable value, 
				//	the returned value for several styles may be their 
				// 	default values, such as 'auto', 'normal', 'inherit',...
				value = el.currentStyle[style];
				if (value.substring(value.length-2,value.length) == "px") {
					alert('style' + style);
					value = value+'px';
				}
			}
		}
		
		// if the value we got from our element has more than 0 characters...
		if(value.length > 0){
		
			// if the value returned has the word px in it, we check for the letter x
			if (value.charAt(value.length-1) == "x") {
			
				// substring()
                //	get all characters from the 0th place to the total String length - 2
				// parseInt() 
                //	Only the first number in the string is returned! 
                //  Leading and trailing spaces are allowed.
				//  If the first character cannot be converted to a number, parseInt() returns NaN.
				value = parseInt(value.substring(0,value.length-2)) 
			} 
		}
		return value;	
	
	} // end getStyleFromCSS()

	function autoExpandContract(el) {

		// get height of the element
		var __heightFromElement = el.offsetHeight;
		
		// get height of the element set in the styles
		var __heightFromCSS = parseInt(getStyleFromCSS(el, 'height'));
		
		// If the height style is set in the CSS and it's bigger than 0px, resize the element to that height
		if (__heightFromCSS > 0) {
			__heightFromElement = __heightFromCSS;
		}

		// adjust the textarea, for all good browsers: overflow:hidden to lose the scrollbars, 
		// for IE use overflowX:auto to let that browser decide whether to show scrollbars
		$(el).setStyle({overflow: 'hidden', overflowX: 'auto'});
		
		// set the width and height to the correct values
		el.style.width = getStyleFromCSS(el, 'width')+'px';
		el.style.height = getStyleFromCSS(el, 'height')+'px';
		
		// create a new element that will be used to track the dimensions
		var dummy_id = Math.floor(Math.random()*99999) + '_dummy';
		var div = document.createElement('div');
		
		// we use setAttribute() here instead of writeAttribute which is a prototype method due to IE6
		div.setAttribute('id',dummy_id);
		document.body.appendChild(div);
		var dummy = $(dummy_id);
	
		// match the new elements style to the el
		dummy.style.fontFamily = getStyleFromCSS(el, 'font-family');
		dummy.style.fontWeight = getStyleFromCSS(el, 'font-weight');
        dummy.style.fontSize = getStyleFromCSS(el, 'font-size')+'px';
        
        // fornicate with IE (not a check for IE7 though, just IE6)
        if (navigator.userAgent.indexOf('MSIE') !=-1) {
            dummy.style.width = getStyleFromCSS(el, 'width');
            
        // Play  nice with the good browsers
        } else {
            dummy.style.width = getStyleFromCSS(el, 'width')+'px';
        }
        dummy.style.padding = getStyleFromCSS(el, 'padding');
        dummy.style.margin = getStyleFromCSS(el, 'margin');
		dummy.style.overflowX = 'auto';
		// hide the created div away
		dummy.style.position = 'absolute';
		dummy.style.top = '0px';
		dummy.style.left = '-9999px';
		dummy.innerHTML = ' 42';
		
		var __lineHeight = dummy.offsetHeight;
		
		var checkExpandContract = function(){
			// place text inside the element in a new var called html
			var html = el.value;
			html = html.replace(/\n/g, '
'); if (dummy.innerHTML != html) { dummy.innerHTML = html; var __dummyHeight = dummy.offsetHeight; var __elHeight = el.offsetHeight; // if the height from our element is not the same as the height from our dummy we just made... // example: 150px != 250px if (__elHeight != __dummyHeight) { // example: 250px > 200px if (__dummyHeight > __heightFromElement) { // example: height = (250+0)px el.style.height = (__dummyHeight+__lineHeight) + 'px'; } else { el.style.height = __heightFromElement+'px'; } } } } var expandElement = function() { interval = window.setInterval(function() {checkExpandContract()}, 250); } var contractElement = function() { clearInterval(interval); } // Put eventListeners to our elements $(el).observe('focus', expandElement); $(el).observe('blur', contractElement); checkExpandContract(); } // end autoExpandContract() });
Copyright notice: I based the code above on another article on javascript expand contract
But I :
  • transformed it to Javascript / Prototype code
  • made it work with IE6
  • documented the code

4. Adding extra functionality

4.1 Highlighting

When an input field or textarea is focused or blurred we change the background color respectively.
Add the class highlight to each input field or textarea

<input type="text" class="highlight" name="yourName" />

And write this Javascript code:

$$('.highlight').each(function(item) {
    item.observe('focus', function(){ 
        item.style.backgroundColor = "#FDFFDE";
    });
    item.observe('blur', function(){ 
        item.style.backgroundColor = "#ffffff";
    });				
});

4.2 Ajaxifying the form

This will display feedback to the user (by using the <div id="feedback"></div>) by means of a famous web 2.0 loading indicator.

function sendForm(event){
    // we stop the default submit behaviour
    Event.stop(event);
    var oOptions = {
        method: "POST",
        parameters: Form.serialize("contactForm"),
        asynchronous: true,
        onFailure: function (oXHR) {
            $('feedback').update(oXHR.statusText);
        },
        onLoading: function (oXHR) {
            $('feedback').update('Sending data ... Loading...');
        },							
        onSuccess: function(oXHR) {
            // Display the result in the div where the form is
            $('feedback').hide();
            $('contact').update(oXHR.responseText);
            /* or use this to display the result in the feedback div, leaving the form visible
            $('feedback').update(oXHR.responseText);   
            */    
        }				
    };
    var oRequest = new Ajax.Updater({success: oOptions.onSuccess.bindAsEventListener(oOptions)}, "processForm.php", oOptions);			 
}
Event.observe('submitButton', 'click', sendForm, false);	

4.3. Processing the form

The code below is for demonstrational purposes only. It will work, but please add more security when deploying this in a live environment

Save as processForm.php and put it in the same directory as your HTML-file.
<?php
if (isset($_POST)) {
    sleep(1);
    echo 'Thanks for getting in touch!';
    echo '

The query string you sent is:
'; foreach($_POST as $key => $value) { echo $key . ': ' . $value . '
'; } } ?>

5. HTML and Javascript for automatically expanding the form

Your final document should look like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<title>Automatically expand or contract a textarea </title>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
		<meta http-equiv="pragma" content="no-cache" />
		<meta name="ROBOTS" content="ALL" />
		<meta name="MSSmartTagsPreventParsing" content="true" />
		<meta name="description" content="" />
		<meta name="keywords" content="" />
		<meta name="AUTHOR" content="" />
		<style type="text/css" media="screen"><!-- 
		body {
			line-height: 15px!important;
			}        
		.demoTextarea, .inputField  {
			width: 350px; 
			border: 1px solid #666; 
			font-size: 100%; 
			font-family:Arial, Helvetica, sans-serif; 
			color: #666;
			}
		.demoTextarea{
			height: 100px;
			}
		--></style>	
		<script language="javascript" type="text/javascript" src="js/prototype.js"></script>         
		<script language="javascript" type="text/javascript" src="js/autoExpandContract.js"></script> 
		<script type="text/javascript">
		// <![CDATA[
		document.observe('dom:loaded', function() {
			$$('.highlight').each(function(item) {
				item.observe('focus', function(){ 
					item.style.backgroundColor = "#FDFFDE";
				});
				item.observe('blur', function(){ 
					item.style.backgroundColor = "#ffffff";
				});				
			});
			function sendForm(event){
				// we stop the default submit behaviour
				Event.stop(event);
				var oOptions = {
					method: "POST",
					parameters: Form.serialize("contactForm"),
					asynchronous: true,
					onFailure: function (oXHR) {
						$('feedback').update(oXHR.statusText);
					},
					onLoading: function (oXHR) {
						$('feedback').update('Sending data ... <img src="images/loading_indicator.gif"  alt="Loading..." />');
					},							
					onSuccess: function(oXHR) {
                    	// Display the result in the div where the form is
						$('feedback').hide();
					   	$('contact').update(oXHR.responseText);
                        /* or use this to display the result in the feedback div, leaving the form visible
					   	$('feedback').update(oXHR.responseText);   
                        */                          
					}				
				};
				var oRequest = new Ajax.Updater({success: oOptions.onSuccess.bindAsEventListener(oOptions)}, "processForm.php", oOptions);			 
			}
			Event.observe('submitButton', 'click', sendForm, false);
		});
		// 
		</script>
	</head>
    <body>
		<div id="feedback"> </div>
        <div id="contact">
            <form id="contactForm">
                <p>Name:<br /><input type="text" class="highlight inputField" name="name" /></p>
                <p>E-mail:<br /><input type="text" class="highlight inputField" name="e-mail" /></p>
                <p>Message:<br /><textarea class="highlight expand demoTextarea" name="message"></textarea></p>
                <p><input type="submit" id="submitButton" value="send contact form" /></p>
            </form>        
		</div>        
    </body>
</html>

6. Demo auto expand and contract a form's textarea

Name:

E-mail:

Message:



7. How did we do for "Javascript Prototype auto expand a form's textarea"?