/*
* Flext - A Mootools Based Flexible TextArea Class
* version 1.1 - for mootools 1.2
* by Graham McNicoll
*
* Copyright 2008-2009 - Education.com
* License: MIT-style license.
*
* Features:
* - Grows text areas when needed
* - Can set a max height to grow to
* - Grows parents if they have a fixed height
* - Ghost text replacement
* - Text input emulation (enter can submit form, instead of new line)
*
* Usage:
*
* include the source somewhere on your page. Textareas must have the class name: 'flext'
* for the class to watch them. Use additional class names to trigger features.
*
* 'growme' - grow the text area
* 'maxheight-[num]' - the max height to grow in pixels (replaces [num] )
* 'stopenter' - stop the enter key
* 'entersubmits' - submit the form when enter is pressed
* 'replaceghosttext' - tries to use the ghosted text features
* 'growparents' - grow the parent elements if needed
*
* if replaceghosttext is on, then you need to add two more attributes to the textarea.
* 'ghosttext' contains a copy of the original ghost text (needed for matching initial conditions),
* and 'ghostclass' which contains a class name to remove when the ghosting is removed (which
* is used to remove ghosting color).
*
* Examples:
*
* A simple growing text area: -
*
*
*
* It will find this text area by the class name, 'flext', and the 'growme'
* class will tell it to grow until the max size, as given by the 'maxheight-[num]'
* class (integer, in pixels).
*
* Textarea which will grow the parent elements (if needed) -
*
*
*
* This is the same as above, except it will also grow any parent elements which
* have fixed heights when the textarea expands ('growparents').
*
*
* Adv. example:
*
*
*
* This example not only grows, but simulates a text input, in that 'enter'
* will not be passed to the textarea ('stopenter') instead it will submit
* the form ('entersubmits'). It also has ghosted text replacement and class
* changing. When this textarea receives focus, it will remove the default
* text (ghosttext property), and remove the class as specified by the
* ghostclass property. Use of these features as currently coded requires
* non valid xhtml, so dont use it if you require valid markup. (its on my list to fix)
*
* You can also instantiate this class manually, by leaving off the 'flext' class from
* any textareas, and instantiate a new class usual with the first variable being the
* textarea element, and the second the options object.
*/
var Flext = new Class({
Implements: Options,
options: {
aniTime: 300, //int (ms) - grow animation time
maxHeight: 0, //int (pixels) - one way to set a max height, if you dont set it via the class.
defaultMaxHeight: 1000, //int (pixels) - if not otherwise set, this is the max height
parentDepth: 6, //int - how many levels up should to check the parent el's height.
//trigger classes:
growClass: 'growme', //string (class name)- grow the text area
enterStoppedClass: 'stopenter', //string (class name)- stop the enter key
enterSubmitsClass: 'entersubmits', //string (class name)- submit the form when enter is pressed
replaceGhostTextClass: 'replaceghosttext', //string (class name)- tries to use the ghosted text features
growParentsClass: 'growparents', //string (class name)- grow the parent elements if needed
//other attributes:
ghostTextAttr: 'ghosttext',
ghostClassAttr: 'ghostclass'
},
initialize: function(el, options) {
this.setOptions(options);
this.el = document.id(el); //the textarea element.
//by default, we will do nothing to the text area unless it has the class...
this.autoGrow = el.hasClass(this.options.growClass);
this.stopEnter = el.hasClass(this.options.enterStoppedClass);
this.enterSubmits = el.hasClass(this.options.enterSubmitsClass);
this.useGhostText = el.hasClass(this.options.replaceGhostTextClass);
this.growParents = el.hasClass(this.options.growParentsClass);
//initialize, and add events:
if(this.autoGrow) {
this.resizer = new Fx.Tween(this.el, {duration: this.options.aniTime});
this.getMaxSize();
this.reachedMax = false;
this.startSize = this.origSize = this.el.getSize().y;
this.vertPadding = this.el.getStyle('padding-top').toInt()+this.el.getStyle('padding-bottom').toInt()+this.el.getStyle('border-top').toInt()+this.el.getStyle('border-bottom').toInt();
this.el.setStyle('overflow', 'hidden');
this.el.addEvents({
'keyup': function(e) {
this.checkSize(e);
}.bind(this),
'change': function(e) {
this.checkSize(e);
}.bind(this),
'click': function(e) {
this.checkSize(e);
}.bind(this)
});
//get inital state:
this.checkSize();
}
//watch this text area: keydown
if(this.stopEnter) {
this.el.addEvent('keydown', function(e) {
if(e.key == 'enter') {
e.stop();
if(this.enterSubmits) {
this.submitForm();
}
}
}.bind(this));
}
//replace ghost text:
if(this.useGhostText) {
this.ghostText = this.el.get(this.options.ghostTextAttr);
this.ghostClass = this.el.get(this.options.ghostClassAttr);
if(this.ghostText) {
//initial states: if populated with something else, remove the class:
if(this.el.value != this.ghostText) {
this.el.removeClass(this.ghostClass);
}
//add events to watch for ghosting:
this.el.addEvents({
//remove the ghosted text when the text area receives focus
'focus': function(e) {
if(this.el.value == this.ghostText) {
this.el.set('value', '');
if(this.ghostClass) {
this.el.removeClass(this.ghostClass);
}
}
}.bind(this),
//put the ghost text back if blur'ed and its empty
'blur': function(e) {
if(this.el.value == '') {
this.el.set('value', this.ghostText);
if(this.ghostClass) {
this.el.addClass(this.ghostClass);
}
}
}.bind(this)
});
}
}
},
getMaxSize: function() {
this.maxSize = this.options.maxHeight;
if(this.maxSize == 0) {
var testmax = this.el.className.match(/maxheight-(\d*)/);
if(testmax) {
this.maxSize = testmax[1];
}
else {
this.maxSize = this.options.defaultMaxHeight; //if one forgets to set a max height via options or class, use a reasonable number.
}
}
},
checkSize: function(e) {
var theSize = this.el.getSize();
var theScrollSize = this.el.getScrollSize();
if(navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { var checksize = (theScrollSize.y); }
else var checksize = (theScrollSize.y+this.vertPadding);
if(checksize > theSize.y) {
//we are scrolling, so grow:
this.resizeIt(theSize, theScrollSize);
}
},
resizeIt: function(theSize, scrollSize) {
var newSize = scrollSize.y;
if((scrollSize.y+this.vertPadding) > this.maxSize && !this.reachedMax) {
//we've reached the max size, grow to max size and make textarea scrollable again:
newSize = this.maxSize;
this.el.setStyle('overflow', '');
this.resizer.start('height', newSize);
if(this.growParents) {
var increasedSize = newSize - this.startSize;
this.resizeParents(this.el, 0, increasedSize);
}
//remember that we've reached the max size:
this.reachedMax = true;
}
if(!this.reachedMax) {
//grow the text area:
var increasedSize = newSize - this.startSize;
if(increasedSize < 0) increasedSize = 0;
this.startSize = newSize;
this.resizer.start('height', newSize);
//resize parent objects if needed:
if(this.growParents) {
this.resizeParents(this.el, 0, increasedSize);
}
}
},
resizeParents: function(el, num, incSize) {
if(num < this.options.parentDepth) {
var newel = el.getParent();
if(newel) {
if(newel.style.height && newel.style.height != '' ) {
if(newel.retrieve('flextAdjusted')) {
var newheight = (newel.getStyle('height').toInt()+incSize);
} else {
newel.store('flextAdjusted', true); //when resizing parents, the first time we enlarge them we have to include vertical padding
var newheight = (newel.getStyle('height').toInt()+incSize+this.vertPadding);
}
newel.setStyle('height', newheight);
}
return this.resizeParents(newel, (num+1), incSize);
}
return true;
} else {
return true;
}
},
submitForm: function() {
var thisForm = this.el.getParent('form');
if(thisForm) {
var formName = thisForm.get('name');
document[formName].submit();
}
}
});
//watch the text areas:
window.addEvent('domready', function() {
$$('textarea.flext').each(function(el, i) {
new Flext(el);
});
});