Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.268
diff -u -p -r1.268 form.inc
--- includes/form.inc	15 Mar 2008 11:02:47 -0000	1.268
+++ includes/form.inc	20 Mar 2008 23:47:31 -0000
@@ -2052,9 +2052,18 @@ function theme_textarea($element) {
     drupal_add_js('misc/textarea.js');
     $class[] = 'resizable';
   }
-
   _form_set_class($element, $class);
-  return theme('form_element', $element, '<textarea cols="'. $element['#cols'] .'" rows="'. $element['#rows'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. drupal_attributes($element['#attributes']) .'>'. check_plain($element['#value']) .'</textarea>');
+
+  // Add dynamic maxlength validation.
+  if (!empty($element['#maxlength'])) {
+    drupal_add_js('misc/textarea.js');
+    // store the maxlength in a javascript variable
+    drupal_add_js(array('textarea_maxlength' => array($element['#id'] => $element['#maxlength'])), 'setting');
+    return theme('form_element', $element, '<textarea cols="'. $element['#cols'] .'" rows="'. $element['#rows'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. drupal_attributes($element['#attributes']) .'>'. check_plain($element['#value']) .'</textarea>');
+  }
+  else {
+    return theme('form_element', $element, '<textarea cols="'. $element['#cols'] .'" rows="'. $element['#rows'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. drupal_attributes($element['#attributes']) .'>'. check_plain($element['#value']) .'</textarea>');
+  }
 }
 
 /**
Index: misc/textarea.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/textarea.js,v
retrieving revision 1.23
diff -u -p -r1.23 textarea.js
--- misc/textarea.js	6 Feb 2008 19:38:26 -0000	1.23
+++ misc/textarea.js	20 Mar 2008 23:47:31 -0000
@@ -33,4 +33,40 @@ Drupal.behaviors.textarea = function(con
       textarea.css('opacity', 1);
     }
   });
+
+  $("textarea:not(.textarea-maxlength-processed)", context).each(function() {
+    $(this).addClass("textarea-maxlength-processed");
+    
+    // Ignore textareas that don't have a maxlength
+    if (!Drupal.settings.textarea_maxlength[$(this).attr('id')]) {
+      return;
+    }
+    
+    // Add a placeholder for the character counter
+    var counterPlaceholder = '<div class="character-count"></div>';
+    $(this).filter(".resizable").before(counterPlaceholder).end().end().not(".resizable").before(counterPlaceholder);
+
+    var validateLength = function() {
+      var maxLength = Drupal.settings.textarea_maxlength[$(this).attr('id')];
+      var length = $(this).val().length;
+      var $placeholder = $(this).siblings(".character-count");
+
+      if (length > maxLength) {
+        $(this).val($(this).val().substring(0, maxLength));
+        length = maxLength;
+        $placeholder.addClass('warning');
+      }
+      else {
+        $placeholder.removeClass('warning');
+      }
+      remaining = maxLength - length;
+      $placeholder.html(length + " / " + maxLength);
+    };
+
+    // Attach to blur because keyup is not fired when pasting via mouse.
+    $(this).blur(validateLength).keyup(validateLength);
+    
+    // Initialize the counter
+    $(this).keyup();
+  });
 };
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.48
diff -u -p -r1.48 system.css
--- modules/system/system.css	9 Jan 2008 09:56:39 -0000	1.48
+++ modules/system/system.css	20 Mar 2008 23:47:31 -0000
@@ -361,6 +361,18 @@ html.js .resizable-textarea textarea {
 }
 
 /*
+** Character counter for text areas using maxlength
+*/
+.character-count {
+  clear: both;
+  float: right;
+  text-align: right;
+  border: 1px solid #ccc;
+  padding: 2px;
+  margin-right: -6px;
+}
+
+/*
 ** Table drag and drop.
 */
 .draggable a.tabledrag-handle {
