[v1.1] Uvumi Textarea

a place to post your own scripts

Moderator: 1.2 Moderators

Forum rules
  • Each Script may have only one thread per mayor release.
  • The owner of the thread should keep the first post up to date (newest version, demo...)
  • The title should include the version -> "[v0.1] Nice mooPlugin"
  • see also the detailed Rules

[v1.1] Uvumi Textarea

Postby horseweapon on Mon Aug 04, 2008 7:22 pm

Hi, some of you might have seen this plugin from the google group, I linked it over there once.
It's just a textarea with extra features : Its size adjusts to the content, and a fancy counter displays your character budget, with a "life bar" style.

Demo, tutorial and download can be found at link bellow

Uvumi Textarea

More plugins coming, including a form validator (yes another one) this textarea can be combined to.
Last edited by horseweapon on Tue Dec 09, 2008 8:01 pm, edited 1 time in total.
User avatar
horseweapon
mootools enthusiast
 
Posts: 393
Joined: Mon Aug 04, 2008 6:52 pm
Location: France

Re: [v1.0.2] Uvumi Textarea

Postby X-trace on Tue Aug 05, 2008 8:58 am

I like the life bar! nice work.

The 'grow' effect seems to slow down my input actions. After adding text with multiple enters I've got to wait a few seconds to see the result.
User avatar
X-trace
mootools enthusiast
 
Posts: 399
Joined: Wed Jul 16, 2008 4:23 pm
Location: Groningen, NL

Re: [v1.0.2] Uvumi Textarea

Postby adamnfish on Mon Aug 11, 2008 4:18 am

Yeah, I agree. Try setting the effect's link property to 'cancel' instead of 'chain' and I reckon that'll sort it!

It's a great script though, thanks! I suspect I'll have use for it in an upcoming project...
User avatar
adamnfish
mootools connoisseurs
 
Posts: 27
Joined: Fri Aug 08, 2008 2:26 am
Location: London, UK

Re: [v1.0.2] Uvumi Textarea

Postby tank on Thu Nov 20, 2008 4:24 pm

I've done a quick scan of the code and I can't figure out where the new height is being calculated. Can anyone shed some light on that for me? Maybe it's just early and I need coffee :)

I'm having an issue where the resize is giving me a bunch of space at the bottom of the text area and I can't figure out where it's coming from.

And just a side note.. It would be nice to have an option to just completely turn off the status bar. As it is. I'm not using it and in injects an element underneath the textarea forcing me to have to deal with removing it/adjusting the margin on the following elements.

The resize works like a champ though. Great script!
tank
mootools fan
 
Posts: 93
Joined: Thu Sep 25, 2008 5:10 pm

Re: [v1.0.2] Uvumi Textarea

Postby tank on Thu Nov 20, 2008 4:35 pm

I think I woke up :)

The Dummies div that are created overwrite the bottom padding and set it at a hard 30px. Which is fine for larger textareas but in my case I have a small one and it doesn't need to be that large. I just changed it to use the lineheight variable that is pulled from the textarea. That way it always gives me one line of padding.

Also it might be a nice feature to run the update height script when Enter is pressed since that would be when I would expect the textarea to grow. But that may be just me.
tank
mootools fan
 
Posts: 93
Joined: Thu Sep 25, 2008 5:10 pm

Re: [v1.0.2] Uvumi Textarea

Postby horseweapon on Thu Nov 20, 2008 6:26 pm

There is big security flaw in that plugin that I found when I started using it for real : that dummy will simply try to convert to html anything you paste in and that includes script tag. I found that error when I pasted embed code from youtube, it actually generated the video player in the dummy and my textarea grew a lot all the sudden. That was really dumb from me. Why was I using set('html') instead of set('text')? I had some problem with set('text') in some browsers. The fix is actually easy, I just had to replace the dummy div with a clone of the textarea

here is the new code, until I get a chance to update the real page

  1.  
  2.  
  3.  
  4. var UvumiTextarea = Class({
  5.  
  6.     Implements:Options,
  7.  
  8.     options:{
  9.         selector:'textarea',    //textareas CSS selector, default 'textarea' select all textboxes in the document
  10.        
  11.         maxChar:1000,           //maximum number of characters per textarea
  12.        
  13.         resizeDuration:500,     //animation duration of progress bar and resizing, in milliseconds
  14.        
  15.         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
  16.        
  17.         maxSize:false,          //Maximum height you can expand the textarea, so it doesn't break your document. When you reach the height, the it won't extend anymore, and a scroll bar will appear instead, if false, no max height
  18.        
  19.         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
  20.        
  21.         classPrefix:'tb'        //The CSS classes associated to the new elements will start with the string defined in this option.
  22.                                 //Usefull if you use this plugin on several pages with different styles. if you have a red theme and a blue theme,
  23.                                 //initialize an instance with classPrefix:'red' and another with classPrefix:'blue', and you'll just have to create a set of CSS rules
  24.                                 //redControls, redProgress, redProgressBar, redCounter and another where you replace red by blue, blueControls, blueProgress...
  25.     },
  26.  
  27.     initialize: function(options){
  28.         this.setOptions(options);
  29.        
  30.         //each textarea will have its own elements, all storred in arrays
  31.         this.tbDummies =[];
  32.         this.tbCounters =[];
  33.         this.tbProgress = [];
  34.         this.tbProgressBar = [];
  35.         window.addEvent('domready',this.domReady.bind(this));   
  36.     },
  37.    
  38.     domReady: function(){
  39.         //the text area array is initialized with an optional CSS selector. The default selector is just 'textarea', affecting all textareas in the document
  40.         this.textareas=$$(this.options.selector);
  41.         this.textareas.each(this.buildProgress,this);
  42.         this.tbProgressEffects = new Fx.Elements(this.tbProgressBar, {
  43.             'link':'cancel'
  44.         });
  45.         this.tbEffects = new Fx.Elements(this.textareas,{
  46.             'duration':this.options.resizeDuration,
  47.             'link':'cancel'
  48.         });
  49.         this.textareas.each(function(el,i){this.update.delay(i*(this.options.resizeDuration+50),this,i);},this);
  50.    
  51.     },
  52.    
  53.     //this functions builds all the new HTML elements and assigns events
  54.     buildProgress: function(textbox,i){
  55.         textbox.setStyle('overflow','hidden');
  56.        
  57.         //if minimum size option is false, we use the original size as minimum.
  58.         if(!this.options.minSize){
  59.             this.options.minSize = textbox.getSize().y;
  60.         }
  61.         this.tbDummies[i] = textbox.clone().addClass('sticker').setStyles({
  62.                 'width':textbox.getStyle('width').toInt(),
  63.                 'position':'absolute',
  64.                 'top':0,
  65.                 'height':this.options.minSize,
  66.                 'left':-3000
  67.         }).store('height',0).inject($(document.body));
  68.        
  69.         textbox.addEvents({
  70.             '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
  71.             'keyup':this.onKeyPress.bindWithEvent(this,i)
  72.         });
  73.  
  74.         this.tbProgress[i]=new Element('div',{
  75.             'class':this.options.classPrefix+'Progress',
  76.             'styles':{
  77.                 'position':'relative',
  78.                 'overflow':'hidden',
  79.                 'display':'block',
  80.                 'position':'relative',
  81.                 'width':textbox.getSize().x-1,
  82.                 'margin':'5px 0 5px '+textbox.getPosition(textbox.getParent()).x+'px'
  83.             }
  84.         }).inject(textbox,'after');
  85.         this.tbProgressBar[i]=new Element('div',{
  86.             'class':this.options.classPrefix+'ProgressBar',
  87.             'styles':{
  88.                 'position':'absolute',
  89.                 'top':0,
  90.                 'left':0,
  91.                 'height':'100%',
  92.                 'width':'100%'
  93.             }
  94.         }).inject(this.tbProgress[i]);
  95.         this.tbCounters[i] = new Element('div', {
  96.             'class':this.options.classPrefix+'Counter',
  97.             'styles':{
  98.                 'position':'absolute',
  99.                 'top':0,
  100.                 'left':0,
  101.                 'height':'100%',
  102.                 'width':'100%',
  103.                 'text-align':'center'
  104.             }
  105.         }).inject(this.tbProgress[i]);
  106.     },
  107.    
  108.     onKeyPress: function(e,i,tab) {
  109.         var event = new Event(e);
  110.         if(event.key == "tab"){
  111.             if(tab){
  112.                 event.preventDefault();
  113.                 this.insertTab(i);
  114.             }
  115.         }
  116.         if(!event.shift && !event.control && !event.alt && !event.meta) this.update(i);
  117.     },
  118.  
  119.     update: function(i) {
  120.         if (this.textareas[i].get('value').length > this.options.maxChar){
  121.             this.textareas[i].set('value', this.textareas[i].get('value').substring(0, this.options.maxChar));
  122.         }
  123.         var count = this.options.maxChar - this.textareas[i].get('value').length;
  124.         var percentage = (count * this.tbProgress[i].getSize().x / this.options.maxChar).toInt();
  125.         var effect = {};
  126.         effect[i]={'width':percentage};
  127.         this.tbProgressEffects.start(effect);
  128.         if (count == 0) {
  129.             var ct = 'No character left';
  130.             this.tbProgress[i].highlight("#f66");
  131.         } else if (count == 1) {
  132.             var ct = '1 character left';
  133.         } else {
  134.             var ct = count + ' characters left';
  135.         }
  136.         this.tbCounters[i].empty().appendText(ct); //hack because of bug in IE
  137.        
  138.         var html = this.textareas[i].get('value');
  139.         this.tbDummies[i].set('value',html);
  140.         var height = (this.tbDummies[i].getScrollSize().y>this.options.minSize?this.tbDummies[i].getScrollSize().y:this.options.minSize);
  141.         if(this.options.maxSize && height>this.options.maxSize){
  142.             height = this.options.maxSize;
  143.         }
  144.         if(this.tbDummies[i].retrieve('height')!=height){
  145.             this.tbDummies[i].store('height',height);
  146.             effect = {};
  147.             effect[i]={'height':height};
  148.             this.textareas[i].setStyle('overflow',(height<this.textareas[i].getScrollSize().y?'auto':'hidden'));
  149.             this.tbEffects.start(effect);
  150.            
  151.         }
  152.     },
  153.    
  154.     insertTab: function(i) {
  155.         if(Browser.Engine.trident) {
  156.             var range = document.selection.createRange();
  157.             range.text = "\t";
  158.         }else{
  159.             var start = this.textareas[i].selectionStart;
  160.             var end = this.textareas[i].selectionEnd;
  161.             var value = this.textareas[i].get('value');
  162.             this.textareas[i].set('value', value.substring(0, start) + "\t" + value.substring(end, value.length));
  163.             start++;
  164.             this.textareas[i].setSelectionRange(start, start);
  165.         }
  166.     }
  167. });
  168.  
User avatar
horseweapon
mootools enthusiast
 
Posts: 393
Joined: Mon Aug 04, 2008 6:52 pm
Location: France

Re: [v1.0.2] Uvumi Textarea

Postby tank on Thu Nov 20, 2008 8:37 pm

ah! very nice. That takes care of several quirks I was seeing.
tank
mootools fan
 
Posts: 93
Joined: Thu Sep 25, 2008 5:10 pm

Re: [v1.0.2] Uvumi Textarea

Postby horseweapon on Tue Dec 09, 2008 8:00 pm

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
  1.  
  2. var UvumiTextarea = Class({
  3.  
  4.     Implements:Options,
  5.  
  6.     options:{
  7.         selector:'textarea',    //textareas CSS selector, default 'textarea' select all textboxes in the document. ALSO ACCEPTS AN ELEMENT OR AN ID
  8.        
  9.         maxChar:1000,           //maximum number of characters per textarea. SET TO 0 OR FALSE TO DESACTIVATE COUNTER
  10.        
  11.         resizeDuration:250,     //animation duration of progress bar and resizing, in milliseconds
  12.        
  13.         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
  14.                
  15.         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
  16.        
  17.         classPrefix:'tb'        //The CSS classes associated to the new elements will start with the string defined in this option.
  18.                                 //Usefull if you use this plugin on several pages with different styles. if you have a red theme and a blue theme,
  19.                                 //initialize an instance with classPrefix:'red' and another with classPrefix:'blue', and you'll just have to create a set of CSS rules
  20.                                 //redControls, redProgress, redProgressBar, redCounter and another where you replace red by blue, blueControls, blueProgress...
  21.     },
  22.  
  23.     initialize: function(options){
  24.         this.setOptions(options);
  25.         //each textarea will have its own elements, all storred in arrays
  26.         this.tbDummies =[];
  27.         this.tbCounters =[];
  28.         this.tbProgress = [];
  29.         this.tbProgressBar = [];
  30.         window.addEvent('domready',this.domReady.bind(this));
  31.     },
  32.    
  33.     domReady: function(){
  34.         //the text area array is initialized with an optional CSS selector. The default selector is just 'textarea', affecting all textareas in the document
  35.         if($(this.options.selector)){
  36.             this.options.selector = $(this.options.selector);
  37.         }
  38.         this.textareas=$$(this.options.selector);
  39.         this.textareas.each(this.buildProgress,this);
  40.         if(this.options.maxChar){
  41.             this.tbProgressEffects = new Fx.Elements(this.tbProgressBar,{
  42.                 duration:'short',
  43.                 link:'cancel'
  44.             });
  45.         }
  46.         this.tbEffects = new Fx.Elements(this.textareas,{
  47.             duration:this.options.resizeDuration,
  48.             link:'cancel'
  49.         });
  50.         this.textareas.each(function(el,i){
  51.             var value = el.get('value');
  52.             if(this.options.maxChar){
  53.                 if(value.length > this.options.maxChar){
  54.                     value = value.substring(0, this.options.maxChar);
  55.                     el.set('value', value);
  56.                 }
  57.                 var count = this.options.maxChar - value.length;
  58.                 var percentage = (count * this.tbProgress[i].getSize().x / this.options.maxChar).toInt();
  59.                 this.tbProgressBar[i].setStyle('width',percentage);
  60.                 if(!count){
  61.                     var ct = 'No character left';
  62.                 }else if(count == 1){
  63.                     var ct = '1 character left';
  64.                 }else{
  65.                     var ct = count + ' characters left';
  66.                 }
  67.                 this.tbCounters[i].set('text',ct);
  68.             }
  69.             this.tbDummies[i].set('value',value);
  70.             var height = (this.tbDummies[i].getScrollSize().y>this.options.minSize?this.tbDummies[i].getScrollSize().y:this.options.minSize);
  71.             if(this.tbDummies[i].retrieve('height')!=height){
  72.                 this.tbDummies[i].store('height',height);
  73.                 el.setStyle('height',height);
  74.             }
  75.         },this);
  76.    
  77.     },
  78.    
  79.     //this functions builds all the new HTML elements and assigns events
  80.     buildProgress: function(textbox,i){
  81.         textbox.setStyle('overflow','hidden');
  82.         //if minimum size option is false, we use the original size as minimum.
  83.         if(!this.options.minSize){
  84.             this.options.minSize = textbox.getSize().y;
  85.         }
  86.        
  87.         //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....
  88.         //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.
  89.         //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.
  90.        
  91.         this.tbDummies[i] = textbox.clone().setStyles({
  92.                 'width':textbox.getStyle('width').toInt(),
  93.                 'position':'absolute',
  94.                 'top':0,
  95.                 'height':this.options.minSize,
  96.                 'left':-3000
  97.         }).store('height',0).inject($(document.body));
  98.        
  99.         textbox.addEvents({
  100.             '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
  101.             'keyup':this.onKeyPress.bindWithEvent(this,i)
  102.         });
  103.        
  104.         if(this.options.maxChar){
  105.             this.tbProgress[i]=new Element('div',{
  106.                 'class':this.options.classPrefix+'Progress',
  107.                 'styles':{
  108.                     'position':'relative',
  109.                     'overflow':'hidden',
  110.                     'display':'block',
  111.                     'position':'relative',
  112.                     'width':textbox.getSize().x-1,
  113.                     'margin':'5px 0 5px '+textbox.getPosition(textbox.getParent()).x+'px'
  114.                 }
  115.             }).inject(textbox,'after');
  116.             this.tbProgressBar[i]=new Element('div',{
  117.                 'class':this.options.classPrefix+'ProgressBar',
  118.                 'styles':{
  119.                     'position':'absolute',
  120.                     'top':0,
  121.                     'left':0,
  122.                     'height':'100%',
  123.                     'width':'100%'
  124.                 }
  125.             }).inject(this.tbProgress[i]);
  126.             this.tbCounters[i] = new Element('div', {
  127.                 'class':this.options.classPrefix+'Counter',
  128.                 'styles':{
  129.                     'position':'absolute',
  130.                     'top':0,
  131.                     'left':0,
  132.                     'height':'100%',
  133.                     'width':'100%',
  134.                     'text-align':'center'
  135.                 }
  136.             }).inject(this.tbProgress[i]);
  137.             this.update = this.updateCounter;
  138.         }else{
  139.             this.update = this.updateNoCounter;
  140.         }
  141.     },
  142.    
  143.     onKeyPress: function(event,i,tab) {
  144.         if(tab && event.key == "tab"){
  145.             event.preventDefault();
  146.             this.insertTab(i);
  147.         }
  148.         if(!event.shift && !event.control && !event.alt && !event.meta){
  149.             this.update(i);
  150.         }
  151.     },
  152.  
  153.     updateCounter: function(i) {
  154.         var value = this.textareas[i].get('value');
  155.         if(value.length > this.options.maxChar){
  156.             value =  value.substring(0, this.options.maxChar);
  157.             this.textareas[i].set('value',value);
  158.         }
  159.         var count = this.options.maxChar - value.length;
  160.         var percentage = (count * this.tbProgress[i].getSize().x / this.options.maxChar).toInt();
  161.         var effect = {};
  162.         effect[i]={'width':percentage};
  163.         this.tbProgressEffects.start(effect);
  164.         if (count == 0) {
  165.             var ct = 'No character left';
  166.             this.tbProgress[i].highlight("#f66");
  167.         }else if (count == 1){
  168.             var ct = '1 character left';
  169.         }else{
  170.             var ct = count + ' characters left';
  171.         }
  172.         this.tbCounters[i].set('text',ct);
  173.         this.updateHeight(i,value);
  174.     },
  175.    
  176.     updateNoCounter:function(i){
  177.         var value = this.textareas[i].get('value');
  178.         this.updateHeight(i,value);
  179.     },
  180.    
  181.     updateHeight: function(i,value){
  182.         this.tbDummies[i].set('value',value);
  183.         var height = (this.tbDummies[i].getScrollSize().y>this.options.minSize?this.tbDummies[i].getScrollSize().y:this.options.minSize);
  184.         if(this.tbDummies[i].retrieve('height')!=height){
  185.             this.tbDummies[i].store('height',height);
  186.             effect = {};
  187.             effect[i]={'height':height};
  188.             this.tbEffects.start(effect);
  189.         }
  190.     },
  191.    
  192.     insertTab: function(i) {
  193.         if(Browser.Engine.trident) {
  194.             var range = document.selection.createRange();
  195.             range.text = "\t";
  196.         }else{
  197.             var start = this.textareas[i].selectionStart;
  198.             var end = this.textareas[i].selectionEnd;
  199.             var value = this.textareas[i].get('value');
  200.             this.textareas[i].set('value', value.substring(0, start) + "\t" + value.substring(end, value.length));
  201.             start++;
  202.             this.textareas[i].setSelectionRange(start, start);
  203.         }
  204.     }
  205. });
  206.  
User avatar
horseweapon
mootools enthusiast
 
Posts: 393
Joined: Mon Aug 04, 2008 6:52 pm
Location: France

Re: [v1.1] Uvumi Textarea

Postby tank on Tue Dec 09, 2008 10:32 pm

seems to work nice so far. I'll keep playing with it but it seems some of the quirks have gone away!
tank
mootools fan
 
Posts: 93
Joined: Thu Sep 25, 2008 5:10 pm

Re: [v1.1] Uvumi Textarea

Postby horseweapon on Wed Dec 10, 2008 6:32 am

Yup that was the point of this update
User avatar
horseweapon
mootools enthusiast
 
Posts: 393
Joined: Mon Aug 04, 2008 6:52 pm
Location: France

Next

Return to Your Scripts

Who is online

Users browsing this forum: No registered users and 3 guests