Hi, here is a new version for the textarea. I fixed more bugs, added features, and removed one. I haven't have time to update my website, but since it's here and ready to go, I thought I'd share the latest version with you guys
I had to remove the maximum height options. It' was actually breaking everything once you reached it: the overflow of the textarea had to be switched from hidden to auto, causing the cursor to automatically jump to the end of the text in most browsers. It generally happens when you type, and if you are typing somewhere in the middle of your text and not at the end, the cursor jumping is very annoying. I'm using it in a real production environment right now, and I can guaranty you that this bugs is way more annoying that now being able to set a height limit. In most cases the character limit does the job to keep the box at a reasonable size.
About the character limit, after several requests, now the whole counter can be turned off. Just set the maxChar value to 0 or false, and the whole counter/character limit is gone. Tada.
Also, I removed the animation when the page load and set the textarea's height and the counter directly to their updated values, because it was annoying and sometime breaking other stuffs if you had other script in the pages.
Anyway, if you are using this script, I highly recommend this update. You're going to need to rebuild the mootools package if you were using the one included with that script because now it needs FX.Tween
-
- var UvumiTextarea = Class({
-
- Implements:Options,
-
- options:{
- selector:'textarea', //textareas CSS selector, default 'textarea' select all textboxes in the document. ALSO ACCEPTS AN ELEMENT OR AN ID
-
- maxChar:1000, //maximum number of characters per textarea. SET TO 0 OR FALSE TO DESACTIVATE COUNTER
-
- resizeDuration:250, //animation duration of progress bar and resizing, in milliseconds
-
- minSize:false, //minimum height in pixels you can reduce the textarea to. If set to false, the default value, the original textarea's height will be used as a minimum
-
- catchTab:true, //if the textarea should override the tab default event and insert a tab in the text. Default is true, but if you're not going to support it on the back-end, you should disable it
-
- classPrefix:'tb' //The CSS classes associated to the new elements will start with the string defined in this option.
- //Usefull if you use this plugin on several pages with different styles. if you have a red theme and a blue theme,
- //initialize an instance with classPrefix:'red' and another with classPrefix:'blue', and you'll just have to create a set of CSS rules
- //redControls, redProgress, redProgressBar, redCounter and another where you replace red by blue, blueControls, blueProgress...
- },
-
- initialize: function(options){
- this.setOptions(options);
- //each textarea will have its own elements, all storred in arrays
- this.tbDummies =[];
- this.tbCounters =[];
- this.tbProgress = [];
- this.tbProgressBar = [];
- window.addEvent('domready',this.domReady.bind(this));
- },
-
- domReady: function(){
- //the text area array is initialized with an optional CSS selector. The default selector is just 'textarea', affecting all textareas in the document
- if($(this.options.selector)){
- this.options.selector = $(this.options.selector);
- }
- this.textareas=$$(this.options.selector);
- this.textareas.each(this.buildProgress,this);
- if(this.options.maxChar){
- this.tbProgressEffects = new Fx.Elements(this.tbProgressBar,{
- duration:'short',
- link:'cancel'
- });
- }
- this.tbEffects = new Fx.Elements(this.textareas,{
- duration:this.options.resizeDuration,
- link:'cancel'
- });
- this.textareas.each(function(el,i){
- var value = el.get('value');
- if(this.options.maxChar){
- if(value.length > this.options.maxChar){
- value = value.substring(0, this.options.maxChar);
- el.set('value', value);
- }
- var count = this.options.maxChar - value.length;
- var percentage = (count * this.tbProgress[i].getSize().x / this.options.maxChar).toInt();
- this.tbProgressBar[i].setStyle('width',percentage);
- if(!count){
- var ct = 'No character left';
- }else if(count == 1){
- var ct = '1 character left';
- }else{
- var ct = count + ' characters left';
- }
- this.tbCounters[i].set('text',ct);
- }
- this.tbDummies[i].set('value',value);
- var height = (this.tbDummies[i].getScrollSize().y>this.options.minSize?this.tbDummies[i].getScrollSize().y:this.options.minSize);
- if(this.tbDummies[i].retrieve('height')!=height){
- this.tbDummies[i].store('height',height);
- el.setStyle('height',height);
- }
- },this);
-
- },
-
- //this functions builds all the new HTML elements and assigns events
- buildProgress: function(textbox,i){
- textbox.setStyle('overflow','hidden');
- //if minimum size option is false, we use the original size as minimum.
- if(!this.options.minSize){
- this.options.minSize = textbox.getSize().y;
- }
-
- //This will not be visible by user. It's div with the exact same specification as the textarea : same size, same font, same padding, same line-height....
- //on every key stroke, the textarea content is copied in this div, and if the div size is different from on previous key stroke, the textarea grow or shrink to this new height.
- //we had to use this hack because if working diretly with the textarea itself, comparing it's height and scroll-height, it wored fine for growing, but there was to good looking way to make it shrink to the right position.
-
- this.tbDummies[i] = textbox.clone().setStyles({
- 'width':textbox.getStyle('width').toInt(),
- 'position':'absolute',
- 'top':0,
- 'height':this.options.minSize,
- 'left':-3000
- }).store('height',0).inject($(document.body));
-
- textbox.addEvents({
- 'keydown':this.onKeyPress.bindWithEvent(this,[i,this.options.catchTab]), // here and like on all the other events, we must use bindWithEvent because we pass an additionnal parameter beside the event object
- 'keyup':this.onKeyPress.bindWithEvent(this,i)
- });
-
- if(this.options.maxChar){
- this.tbProgress[i]=new Element('div',{
- 'class':this.options.classPrefix+'Progress',
- 'styles':{
- 'position':'relative',
- 'overflow':'hidden',
- 'display':'block',
- 'position':'relative',
- 'width':textbox.getSize().x-1,
- 'margin':'5px 0 5px '+textbox.getPosition(textbox.getParent()).x+'px'
- }
- }).inject(textbox,'after');
- this.tbProgressBar[i]=new Element('div',{
- 'class':this.options.classPrefix+'ProgressBar',
- 'styles':{
- 'position':'absolute',
- 'top':0,
- 'left':0,
- 'height':'100%',
- 'width':'100%'
- }
- }).inject(this.tbProgress[i]);
- this.tbCounters[i] = new Element('div', {
- 'class':this.options.classPrefix+'Counter',
- 'styles':{
- 'position':'absolute',
- 'top':0,
- 'left':0,
- 'height':'100%',
- 'width':'100%',
- 'text-align':'center'
- }
- }).inject(this.tbProgress[i]);
- this.update = this.updateCounter;
- }else{
- this.update = this.updateNoCounter;
- }
- },
-
- onKeyPress: function(event,i,tab) {
- if(tab && event.key == "tab"){
- event.preventDefault();
- this.insertTab(i);
- }
- if(!event.shift && !event.control && !event.alt && !event.meta){
- this.update(i);
- }
- },
-
- updateCounter: function(i) {
- var value = this.textareas[i].get('value');
- if(value.length > this.options.maxChar){
- value = value.substring(0, this.options.maxChar);
- this.textareas[i].set('value',value);
- }
- var count = this.options.maxChar - value.length;
- var percentage = (count * this.tbProgress[i].getSize().x / this.options.maxChar).toInt();
- var effect = {};
- effect[i]={'width':percentage};
- this.tbProgressEffects.start(effect);
- if (count == 0) {
- var ct = 'No character left';
- this.tbProgress[i].highlight("#f66");
- }else if (count == 1){
- var ct = '1 character left';
- }else{
- var ct = count + ' characters left';
- }
- this.tbCounters[i].set('text',ct);
- this.updateHeight(i,value);
- },
-
- updateNoCounter:function(i){
- var value = this.textareas[i].get('value');
- this.updateHeight(i,value);
- },
-
- updateHeight: function(i,value){
- this.tbDummies[i].set('value',value);
- var height = (this.tbDummies[i].getScrollSize().y>this.options.minSize?this.tbDummies[i].getScrollSize().y:this.options.minSize);
- if(this.tbDummies[i].retrieve('height')!=height){
- this.tbDummies[i].store('height',height);
- effect = {};
- effect[i]={'height':height};
- this.tbEffects.start(effect);
- }
- },
-
- insertTab: function(i) {
- if(Browser.Engine.trident) {
- var range = document.selection.createRange();
- range.text = "\t";
- }else{
- var start = this.textareas[i].selectionStart;
- var end = this.textareas[i].selectionEnd;
- var value = this.textareas[i].get('value');
- this.textareas[i].set('value', value.substring(0, start) + "\t" + value.substring(end, value.length));
- start++;
- this.textareas[i].setSelectionRange(start, start);
- }
- }
- });
-