Renamed jquery.growfield.js to jquery-growfield.js.
authorDaniel Diniz <ajaksu@gmail.com>
Fri, 26 Jun 2009 21:54:46 +0200
changeset 2421 0979e7af115f
parent 2420 645f4de26f99
child 2422 44c500fc0eca
Renamed jquery.growfield.js to jquery-growfield.js. Reviewed by: Lennard de Rijk
app/jquery/jquery-growfield.js
app/jquery/jquery.growfield.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/jquery/jquery-growfield.js	Fri Jun 26 21:54:46 2009 +0200
@@ -0,0 +1,581 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009 Johann Kuindji
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author Johann Kuindji, Dmitriy Likhten
+ * http://code.google.com/p/jquery-growfield/
+ */
+(function($) {
+if ($.support === undefined) {
+    $.support = { boxModel: $.boxModel };
+}
+var windowLoaded = false;
+$(window).one('load', function(){ windowLoaded=true; });
+
+// we need to adapt jquery animations for textareas.
+// by default, it changes display to 'block' if we're trying to
+// change width or height. We have to prevent this.
+// THIS WILL NOT ALTER JQUERY ORIGINAL BEHAVIORS, IT WILL HOWEVER ADD
+// SOME SO THAT GROWFIELD ANIMATIONS WORK CORRECTLY.
+$.fx.prototype.originalUpdate = $.fx.prototype.update;
+$.fx.prototype.update = false;
+$.fx.prototype.update = function () {
+    if (!this.options.inline) {
+        return this.originalUpdate.call(this);
+    }
+    if ( this.options.step ) {
+        this.options.step.call( this.elem, this.now, this );
+    }
+    (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+};
+
+$.growfield = function(dom,options){
+    // Extend ptt(prototype) with our own private variables/
+    // shared's functions are re-referenced and not cloned so
+    // memory is kept at a minimum.
+    var that = $.extend({
+        dom: dom,
+        o: $(dom),
+        enabled: false,
+        dummy: false,
+        busy: false,
+        initial: false,
+        sizseRelated: false,
+        prevH: false,
+        firstH: false,
+        restoreH: false,
+        opt: $.extend({},$.fn.growfield.defaults,options)
+    },$.growfield.ptt);
+
+    return that;
+};
+
+//-----------------------------------------------------
+// This is the base class for all $.growfield objects
+// (their prototype)
+//-----------------------------------------------------
+$.growfield.ptt = (function(){
+    //-----------------------------------------------------
+    //EVENT HANDLERS for dealing with the growfield object
+    //-----------------------------------------------------
+    var manualKeyUp = function(e) {
+        var obj = e.data;
+        if (e.ctrlKey && (e.keyCode == 38 || e.keyCode == 40)){
+            obj.update(
+                obj.o.outerHeight() + (obj.opt.step*( e.keyCode==38? -1: 1)),
+                obj.opt.animate
+            );
+        }
+    };
+
+    var keyUp = function(e) {
+        var obj = e.data;
+        if (!obj.busy){
+            if ($.inArray(e.keyCode, [37,38,39,40]) === -1) {
+                obj.update(obj.getDummyHeight(), obj.opt.animate);
+            }
+        }
+        return true;
+    };
+
+    var focus = function(e) {
+        var obj = e.data;
+        if (!obj.busy) {
+            if (obj.opt.restore) {
+                obj.update(obj.dummy ? obj.getDummyHeight() : obj.restoreH, obj.opt.animate, 'growback');
+            }
+        }
+    };
+
+    var blur = function(e) {
+        var obj = e.data;
+        if (!obj.busy) {
+            if (obj.opt.restore) {
+                obj.update(0, obj.opt.animate, 'restore');
+            }
+        }
+    };
+
+    var prepareSizeRelated = function(e) {
+        var obj = e.data;
+        var o = obj.o;
+        var opt = obj.opt;
+
+        if (!opt.min) {
+            opt.min = parseInt(o.css('min-height'), 10) || obj.firstH || parseInt(o.height(), 10) || 20;
+            if (opt.min <= 0) {
+                opt.min = 20; // opera fix
+            }
+            if (!obj.firstH) {
+                obj.firstH = opt.min;
+            }
+        }
+        if (!opt.max) {
+            opt.max = parseInt(o.css('max-height'), 10) || false;
+            if (opt.max <= 0) {
+                opt.max = false; // opera fix
+            }
+        }
+        if (!opt.step) {
+            opt.step = parseInt(o.css('line-height'), 10) || parseInt(o.css('font-size'), 10) || 20;
+        }
+
+        var sr = {
+            pt: parseInt(o.css('paddingTop'), 10)||0,
+            pb: parseInt(o.css('paddingBottom'), 10)||0,
+            bt: parseInt(o.css('borderTopWidth'), 10)||0,
+            bb: parseInt(o.css('borderBottomWidth'), 10)||0,
+            lh: parseInt(o.css('lineHeight'), 10) || false,
+            fs: parseInt(o.css('fontSize'), 10) || false
+        };
+
+        obj.sizeRelated = sr;
+    };
+
+    /**
+     * Create a dummy if one does not yet exist.
+     */
+    var createDummy = function(e) {
+        var obj = e.data;
+        if(!obj.dummy){
+            var val = obj.o.val();
+            // we need dummy to calculate scrollHeight
+            // (there are some tricks that can't be applied to the textarea itself, otherwise user will see it)
+            // Also, dummy must be a textarea too, and must be placed at the same position in DOM
+            // in order to keep all the inherited styles
+            var dummy = obj.o.clone();
+            dummy.addClass('growfieldDummy');
+            dummy.attr('tabindex', -9999);
+            dummy.css({
+                position: 'absolute',
+                left: -9999,
+                top: 0,
+                height: '20px',
+                resize: 'none'});
+            // The dummy must be inserted after otherwise google chrome will
+            // focus on the dummy instead of on the actual text area, focus will always
+            // be lost.
+            dummy.insertAfter(obj.o);
+            dummy.show();
+
+            // if there is no initial value, we have to add some text, otherwise textarea will jitter
+            // at the first keydown
+            if (!val) {
+                dummy.val('dummy text');
+            }
+            obj.dummy = dummy;
+            // lets set the initial height
+            obj.update((!$.trim(val) || obj.opt.restore) ? 0 : obj.getDummyHeight(), false);
+        }
+    };
+
+    /**
+     * Remove the dummy if one exists
+     */
+    var removeDummy = function(e) {
+        obj = e.data;
+        if(obj.dummy){
+            obj.dummy.remove();
+            delete obj.dummy;
+        }
+    };
+
+    //-----------------------------------------------------
+    // END EVENT HANDLERS
+    //-----------------------------------------------------
+
+    // This will bind to $(document).ready if the height is loaded
+    // or a window.load event already occurred.
+    // OR it will just bind to the window.load event.
+    var executeWhenReady = function(data,fn){
+        if (data.o.height() !== 0 || windowLoaded) {
+            $(document).ready(function(){
+                fn({data:data});
+            });
+        }
+        else {
+            $(window).one('load', data, fn);
+        }
+    };
+
+    //-----------------------------------------------------
+    // Public methods.
+    //-----------------------------------------------------
+    var that = {
+        // Toggle the functionality.
+        // enable or true will enable growfield
+        // disable or false will disable growfield
+        toggle: function(mode) {
+            if ((mode=='disable' || mode===false)&&this.enabled) {
+                this.unbind();
+            }
+            else if ((mode=='enable' || mode===true)&&!this.enabled) {
+                this.bind();
+            }
+            return this;
+        },
+
+        // Bind all growfield events to the object.
+        bind: function(){
+            executeWhenReady(this,prepareSizeRelated);
+            var opt = this.opt;
+            var o = this.o;
+
+            // auto mode, textarea grows as you type
+            if (opt.auto) {
+
+                o.bind('keyup.growfield', this, keyUp);
+                this.initial = {
+                    overflow: this.o.css('overflow'),
+                    cssResize: this.o.css('resize')
+                };
+                // We want to ensure that safari and google chrome do not allow
+                // the user to drag-to-resize the field. This should only be enabled
+                // if auto mode is disabled.
+                if ($.browser.safari) {
+                    o.css('resize', 'none');
+                }
+                o.css('overflow','hidden');
+
+                o.bind('focus.growfield', this, createDummy);
+                // all styles must be loaded before prepare elements
+                // we need to ensure the dummy exists at least for a short
+                // time so that we can calculate the initial state...
+                executeWhenReady(this, createDummy);
+                executeWhenReady(this, removeDummy);
+            }
+            // manual mode, textarea grows as you type ctrl + up|down
+            else {
+                o.bind('keydown.growfield', this, manualKeyUp);
+                o.css('overflow-y', 'auto');
+                executeWhenReady(this,function(e){
+                    e.data.update(e.data.o.height());
+                });
+            }
+            o.bind('focus.growfield', this, focus);
+            o.bind('blur.growfield', this, blur);
+            o.bind('blur.growfield', this, removeDummy);
+
+            // Custom events provided in options
+            if (opt.onHeightChange) {
+                o.bind('onHeightChange.growfield', opt.onHeightChange);
+            }
+            if (opt.onRestore) {
+                o.bind('onRestore.growfield', opt.onRestore);
+            }
+            if (opt.onGrowBack) {
+                o.bind('onGrowBack.growfield', opt.onGrowBack);
+            }
+
+            this.enabled = true;
+
+            return this;
+        },
+
+        // Unbind all growfield events from the object (including custom events)
+        unbind: function() {
+            removeDummy({data:this});
+            this.o.unbind('.growfield');
+            this.o.css('overflow', this.initial.overflow);
+            if ($.browser.safari) {
+                this.o.css('resize', this.initial.cssResize);
+            }
+            this.enabled = false;
+
+            return this;
+        },
+
+        // Trigger custom events according to updateMode
+        triggerEvents: function(updateMode) {
+            var o = this.o;
+            o.trigger('onHeightChange.growfield');
+            if (updateMode == 'restore') {
+                o.trigger('onRestore.growfield');
+            }
+            if (updateMode == 'growback') {
+                o.trigger('onGrowBack.growfield');
+            }
+        },
+
+        update: function(h, animate, updateMode) {
+            var sr = this.sizeRelated;
+            var val = this.o.val();
+            var opt = this.opt;
+            var dom = this.dom;
+            var o = this.o;
+            var th = this;
+            var prev = this.prevH;
+            var noHidden = !opt.auto;
+            var noFocus = opt.auto;
+
+            h = this.convertHeight(Math.round(h), 'inner');
+            // get the right height according to min and max value
+            h = opt.min > h ? opt.min :
+                  opt.max && h > opt.max ? opt.max :
+                  opt.auto && !val ? opt.min : h;
+
+            if (opt.max && opt.auto) {
+                if (prev != opt.max && h == opt.max) { // now we reached maximum height
+                    o.css('overflow-y', 'scroll');
+                    if (!opt.animate) {
+                        o.focus(); // browsers do loose cursor after changing overflow :(
+                    }
+                    noHidden = true;
+                    noFocus = false;
+                }
+                if (prev == opt.max && h < opt.max) {
+                    o.css('overflow-y', 'hidden');
+                    if (!opt.animate) {
+                        o.focus();
+                    }
+                    noFocus = false;
+                }
+            }
+
+            if (h == prev) {
+                return true;
+            }
+            // in case of restore in manual mode we have to store
+            // previous height (we can't get it from dummy)
+            if (!opt.auto && updateMode == 'restore') {
+                this.restoreH = this.convertHeight(this.prevH, 'outer');
+            }
+            this.prevH = h;
+
+            if (animate) {
+                th.busy = true;
+                o.animate({height: h}, {
+                    duration: opt.animate,
+                    easing: ($.easing ? opt.easing : null),
+                    overflow: null,
+                    inline: true, // this option isn't jquery's. I added it by myself, see above
+                    complete: function(){
+                        // safari/chrome fix
+                        // somehow textarea turns to overflow:scroll after animation
+                        // i counldn't find it in jquery fx :(, so it looks like some bug
+                        if (!noHidden) {
+                            o.css('overflow', 'hidden');
+                        }
+                        // but if we still need to change overflow (due to opt.max option)
+                        // we have to invoke focus() event, otherwise browser will loose cursor
+                        if (!noFocus && updateMode != 'restore') {
+                            o.focus();
+                        }
+                        if (updateMode == 'growback') {
+                            dom.scrollTop = dom.scrollHeight;
+                        }
+                        th.busy = false;
+                        th.triggerEvents(updateMode);
+                    },
+                    queue: false
+                });
+            } else {
+                dom.style.height = h+'px';
+                this.triggerEvents(updateMode);
+            }
+        },
+
+        getDummyHeight: function() {
+            var val = this.o.val();
+            var h = 0;
+            var sr = this.sizeRelated;
+            var add = "\n111\n111";
+
+            // Safari has some defect with double new line symbol at the end
+            // It inserts additional new line even if you have only one
+            // But that't not the point :)
+            // Another question is how much pixels to keep at the bottom of textarea.
+            // We'll kill many rabbits at the same time by adding two new lines at the end
+            // (but if we have font-size and line-height defined, we'll add two line-heights)
+            if ($.browser.safari) {
+                val = val.substring(0, val.length-1); // safari has an additional new line ;(
+            }
+
+            if (!sr.lh || !sr.fs) {
+                val += add;
+            }
+
+            this.dummy.val(val);
+
+            // IE requires to change height value in order to recalculate scrollHeight.
+            // otherwise it stops recalculating scrollHeight after some magical number of pixels
+            if ($.browser.msie) {
+                this.dummy[0].style.height = this.dummy[0].scrollHeight+'px';
+            }
+
+            h = this.dummy[0].scrollHeight;
+
+            // if line-height is greater than font-size we'll add line-height + font-size
+            // otherwise font-size * 2
+            // there is no special logic in this behavior, it's been developed from visual testing
+            if (sr.lh && sr.fs) {
+                h += sr.lh > sr.fs ? sr.lh+sr.fs :  sr.fs * 2;
+            }
+
+            // now we have to minimize dummy back, or we'll get wrong scrollHeight next time
+            //if ($.browser.msie) {
+            //    this.dummy[0].style.height = '20px'; // random number
+            //}
+
+            return h;
+        },
+
+        convertHeight: function(h, to) {
+            var sr = this.sizeRelated, mod = (to=='inner' ? -1 : 1), bm = $.support.boxModel;
+            // what we get here in 'h' is scrollHeight value.
+            // so we need to subtract paddings not because of boxModel,
+            // but only if browser includes them to the scroll height (which is not defined by box model)
+            return h
+                + (bm ? sr.bt : 0) * mod
+                + (bm ? sr.bb : 0) * mod
+                + (bm ? sr.pt : 0) * mod
+                + (bm ? sr.pb : 0) * mod;
+        }
+
+    };
+
+    return that;
+})();
+
+/**
+ * The growfield function. This will make a textarea a growing text area.
+ *
+ * @param {Object} options - See API for details on possible paramaters.
+ */
+$.fn.growfield = function(options) {
+    // enable/disable is same thing as true/false
+    switch(options){
+        case 'enable':
+            options = true;
+            break;
+        case 'disable':
+            options = false;
+            break;
+    }
+
+    // we need to know what was passed as the options
+    var tp = typeof options;
+
+    // These variables are used to reduce string comparisons
+    // happening over and over.
+    var conditions = {
+        bool: tp == 'boolean',
+        string: tp == 'string',
+        object: tp == 'object',
+        restart: options == 'restart',
+        destroy: options == 'destroy'
+    };
+
+    // If the type of the options is a string
+    // and is not one of the pre-defined ones, then
+    // options is a preset.
+    if(conditions.string && !conditions.destroy && !conditions.restart){
+        options = $.fn.growfield.presets[options];
+        // change to new conditions
+        conditions.string = false;
+        conditions.object = true;
+    }
+
+    // completely remove growfield from the dom elements
+    if (conditions.destroy) {
+        this.each(function() {
+            var self = $(this);
+            var gf = self.data('growfield');
+            if (gf !== undefined) {
+                gf.unbind();
+                self.removeData('growfield');
+            }
+        });
+    }
+    // Apply growfield
+    else {
+        var textareaRegex = /textarea/i;
+        this.each(function() {
+            // only deal with textareas which are not dummy fields.
+            if (textareaRegex.test(this.tagName) && !$(this).hasClass('growfieldDummy')) {
+                var o = $(this);
+                var gf = o.data('growfield');
+                // Create the new options
+                if (gf === undefined) {
+                    gf = $.growfield(this,options);
+                    o.data('growfield', gf);
+
+                    // Bind only if the options is not a boolean
+                    // or is not "false". Because options = a false boolean
+                    // indicates intial bind should not happen.
+                    if(!conditions.bool || options){
+                        gf.bind();
+                    }
+                }
+                // Otherwise apply actions based on the options provided
+                else {
+                    // If new options provided, set them
+                    if(conditions.object && options) {
+                        $.extend(gf.opt,options);
+                    }
+                    // If toggling enable/disable then do it
+                    else if (conditions.bool) {
+                        gf.toggle(options);
+                    }
+                    // If restarting, restart
+                    else if (conditions.restart) {
+                        gf.unbind();
+                        gf.bind();
+                    }
+                }
+            }
+        });
+    }
+
+    return this;
+};
+
+/**
+ * These are the default options to use, unless specified when invoking growfield.
+ */
+$.fn.growfield.defaults ={
+    // Should the growfield automatically expand?
+    auto: true,
+    // The animation speed for expanding (false = off)
+    animate: 100,
+    // The easiny function to use, if the jquery.easing plugin is not present during
+    // execution, this will always be treated as null regardless of the set value
+    easing: null,
+    // The minimum height (defaults to CSS min-height, or the current height of the element)
+    min: false,
+    // The maximum height (defaults to CSS max-height, or unlimited)
+    max: false,
+    // Should the element restore to it's original size after focus is lost?
+    restore: false,
+    // How many pixels to expand when the user is about to have to scroll. Defaults to 1 line.
+    step: false
+};
+
+/**
+ * These are presets. The presets are indexed by name containing different preset
+ * option objects. When growfield is invoked with the preset's name, that options object
+ * is loaded without having to be specified each time.
+ */
+$.fn.growfield.presets = {};
+
+})(jQuery);
+
--- a/app/jquery/jquery.growfield.js	Tue Jun 23 20:54:03 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,581 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2009 Johann Kuindji
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author Johann Kuindji, Dmitriy Likhten
- * http://code.google.com/p/jquery-growfield/
- */
-(function($) {
-if ($.support === undefined) {
-    $.support = { boxModel: $.boxModel };
-}
-var windowLoaded = false;
-$(window).one('load', function(){ windowLoaded=true; });
-
-// we need to adapt jquery animations for textareas.
-// by default, it changes display to 'block' if we're trying to
-// change width or height. We have to prevent this.
-// THIS WILL NOT ALTER JQUERY ORIGINAL BEHAVIORS, IT WILL HOWEVER ADD
-// SOME SO THAT GROWFIELD ANIMATIONS WORK CORRECTLY.
-$.fx.prototype.originalUpdate = $.fx.prototype.update;
-$.fx.prototype.update = false;
-$.fx.prototype.update = function () {
-    if (!this.options.inline) {
-        return this.originalUpdate.call(this);
-    }
-    if ( this.options.step ) {
-        this.options.step.call( this.elem, this.now, this );
-    }
-    (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
-};
-
-$.growfield = function(dom,options){
-    // Extend ptt(prototype) with our own private variables/
-    // shared's functions are re-referenced and not cloned so
-    // memory is kept at a minimum.
-    var that = $.extend({
-        dom: dom,
-        o: $(dom),
-        enabled: false,
-        dummy: false,
-        busy: false,
-        initial: false,
-        sizseRelated: false,
-        prevH: false,
-        firstH: false,
-        restoreH: false,
-        opt: $.extend({},$.fn.growfield.defaults,options)
-    },$.growfield.ptt);
-
-    return that;
-};
-
-//-----------------------------------------------------
-// This is the base class for all $.growfield objects
-// (their prototype)
-//-----------------------------------------------------
-$.growfield.ptt = (function(){
-    //-----------------------------------------------------
-    //EVENT HANDLERS for dealing with the growfield object
-    //-----------------------------------------------------
-    var manualKeyUp = function(e) {
-        var obj = e.data;
-        if (e.ctrlKey && (e.keyCode == 38 || e.keyCode == 40)){
-            obj.update(
-                obj.o.outerHeight() + (obj.opt.step*( e.keyCode==38? -1: 1)),
-                obj.opt.animate
-            );
-        }
-    };
-
-    var keyUp = function(e) {
-        var obj = e.data;
-        if (!obj.busy){
-            if ($.inArray(e.keyCode, [37,38,39,40]) === -1) {
-                obj.update(obj.getDummyHeight(), obj.opt.animate);
-            }
-        }
-        return true;
-    };
-
-    var focus = function(e) {
-        var obj = e.data;
-        if (!obj.busy) {
-            if (obj.opt.restore) {
-                obj.update(obj.dummy ? obj.getDummyHeight() : obj.restoreH, obj.opt.animate, 'growback');
-            }
-        }
-    };
-
-    var blur = function(e) {
-        var obj = e.data;
-        if (!obj.busy) {
-            if (obj.opt.restore) {
-                obj.update(0, obj.opt.animate, 'restore');
-            }
-        }
-    };
-
-    var prepareSizeRelated = function(e) {
-        var obj = e.data;
-        var o = obj.o;
-        var opt = obj.opt;
-
-        if (!opt.min) {
-            opt.min = parseInt(o.css('min-height'), 10) || obj.firstH || parseInt(o.height(), 10) || 20;
-            if (opt.min <= 0) {
-                opt.min = 20; // opera fix
-            }
-            if (!obj.firstH) {
-                obj.firstH = opt.min;
-            }
-        }
-        if (!opt.max) {
-            opt.max = parseInt(o.css('max-height'), 10) || false;
-            if (opt.max <= 0) {
-                opt.max = false; // opera fix
-            }
-        }
-        if (!opt.step) {
-            opt.step = parseInt(o.css('line-height'), 10) || parseInt(o.css('font-size'), 10) || 20;
-        }
-
-        var sr = {
-            pt: parseInt(o.css('paddingTop'), 10)||0,
-            pb: parseInt(o.css('paddingBottom'), 10)||0,
-            bt: parseInt(o.css('borderTopWidth'), 10)||0,
-            bb: parseInt(o.css('borderBottomWidth'), 10)||0,
-            lh: parseInt(o.css('lineHeight'), 10) || false,
-            fs: parseInt(o.css('fontSize'), 10) || false
-        };
-
-        obj.sizeRelated = sr;
-    };
-
-    /**
-     * Create a dummy if one does not yet exist.
-     */
-    var createDummy = function(e) {
-        var obj = e.data;
-        if(!obj.dummy){
-            var val = obj.o.val();
-            // we need dummy to calculate scrollHeight
-            // (there are some tricks that can't be applied to the textarea itself, otherwise user will see it)
-            // Also, dummy must be a textarea too, and must be placed at the same position in DOM
-            // in order to keep all the inherited styles
-            var dummy = obj.o.clone();
-            dummy.addClass('growfieldDummy');
-            dummy.attr('tabindex', -9999);
-            dummy.css({
-                position: 'absolute',
-                left: -9999,
-                top: 0,
-                height: '20px',
-                resize: 'none'});
-            // The dummy must be inserted after otherwise google chrome will
-            // focus on the dummy instead of on the actual text area, focus will always
-            // be lost.
-            dummy.insertAfter(obj.o);
-            dummy.show();
-
-            // if there is no initial value, we have to add some text, otherwise textarea will jitter
-            // at the first keydown
-            if (!val) {
-                dummy.val('dummy text');
-            }
-            obj.dummy = dummy;
-            // lets set the initial height
-            obj.update((!$.trim(val) || obj.opt.restore) ? 0 : obj.getDummyHeight(), false);
-        }
-    };
-
-    /**
-     * Remove the dummy if one exists
-     */
-    var removeDummy = function(e) {
-        obj = e.data;
-        if(obj.dummy){
-            obj.dummy.remove();
-            delete obj.dummy;
-        }
-    };
-
-    //-----------------------------------------------------
-    // END EVENT HANDLERS
-    //-----------------------------------------------------
-
-    // This will bind to $(document).ready if the height is loaded
-    // or a window.load event already occurred.
-    // OR it will just bind to the window.load event.
-    var executeWhenReady = function(data,fn){
-        if (data.o.height() !== 0 || windowLoaded) {
-            $(document).ready(function(){
-                fn({data:data});
-            });
-        }
-        else {
-            $(window).one('load', data, fn);
-        }
-    };
-
-    //-----------------------------------------------------
-    // Public methods.
-    //-----------------------------------------------------
-    var that = {
-        // Toggle the functionality.
-        // enable or true will enable growfield
-        // disable or false will disable growfield
-        toggle: function(mode) {
-            if ((mode=='disable' || mode===false)&&this.enabled) {
-                this.unbind();
-            }
-            else if ((mode=='enable' || mode===true)&&!this.enabled) {
-                this.bind();
-            }
-            return this;
-        },
-
-        // Bind all growfield events to the object.
-        bind: function(){
-            executeWhenReady(this,prepareSizeRelated);
-            var opt = this.opt;
-            var o = this.o;
-
-            // auto mode, textarea grows as you type
-            if (opt.auto) {
-
-                o.bind('keyup.growfield', this, keyUp);
-                this.initial = {
-                    overflow: this.o.css('overflow'),
-                    cssResize: this.o.css('resize')
-                };
-                // We want to ensure that safari and google chrome do not allow
-                // the user to drag-to-resize the field. This should only be enabled
-                // if auto mode is disabled.
-                if ($.browser.safari) {
-                    o.css('resize', 'none');
-                }
-                o.css('overflow','hidden');
-
-                o.bind('focus.growfield', this, createDummy);
-                // all styles must be loaded before prepare elements
-                // we need to ensure the dummy exists at least for a short
-                // time so that we can calculate the initial state...
-                executeWhenReady(this, createDummy);
-                executeWhenReady(this, removeDummy);
-            }
-            // manual mode, textarea grows as you type ctrl + up|down
-            else {
-                o.bind('keydown.growfield', this, manualKeyUp);
-                o.css('overflow-y', 'auto');
-                executeWhenReady(this,function(e){
-                    e.data.update(e.data.o.height());
-                });
-            }
-            o.bind('focus.growfield', this, focus);
-            o.bind('blur.growfield', this, blur);
-            o.bind('blur.growfield', this, removeDummy);
-
-            // Custom events provided in options
-            if (opt.onHeightChange) {
-                o.bind('onHeightChange.growfield', opt.onHeightChange);
-            }
-            if (opt.onRestore) {
-                o.bind('onRestore.growfield', opt.onRestore);
-            }
-            if (opt.onGrowBack) {
-                o.bind('onGrowBack.growfield', opt.onGrowBack);
-            }
-
-            this.enabled = true;
-
-            return this;
-        },
-
-        // Unbind all growfield events from the object (including custom events)
-        unbind: function() {
-            removeDummy({data:this});
-            this.o.unbind('.growfield');
-            this.o.css('overflow', this.initial.overflow);
-            if ($.browser.safari) {
-                this.o.css('resize', this.initial.cssResize);
-            }
-            this.enabled = false;
-
-            return this;
-        },
-
-        // Trigger custom events according to updateMode
-        triggerEvents: function(updateMode) {
-            var o = this.o;
-            o.trigger('onHeightChange.growfield');
-            if (updateMode == 'restore') {
-                o.trigger('onRestore.growfield');
-            }
-            if (updateMode == 'growback') {
-                o.trigger('onGrowBack.growfield');
-            }
-        },
-
-        update: function(h, animate, updateMode) {
-            var sr = this.sizeRelated;
-            var val = this.o.val();
-            var opt = this.opt;
-            var dom = this.dom;
-            var o = this.o;
-            var th = this;
-            var prev = this.prevH;
-            var noHidden = !opt.auto;
-            var noFocus = opt.auto;
-
-            h = this.convertHeight(Math.round(h), 'inner');
-            // get the right height according to min and max value
-            h = opt.min > h ? opt.min :
-                  opt.max && h > opt.max ? opt.max :
-                  opt.auto && !val ? opt.min : h;
-
-            if (opt.max && opt.auto) {
-                if (prev != opt.max && h == opt.max) { // now we reached maximum height
-                    o.css('overflow-y', 'scroll');
-                    if (!opt.animate) {
-                        o.focus(); // browsers do loose cursor after changing overflow :(
-                    }
-                    noHidden = true;
-                    noFocus = false;
-                }
-                if (prev == opt.max && h < opt.max) {
-                    o.css('overflow-y', 'hidden');
-                    if (!opt.animate) {
-                        o.focus();
-                    }
-                    noFocus = false;
-                }
-            }
-
-            if (h == prev) {
-                return true;
-            }
-            // in case of restore in manual mode we have to store
-            // previous height (we can't get it from dummy)
-            if (!opt.auto && updateMode == 'restore') {
-                this.restoreH = this.convertHeight(this.prevH, 'outer');
-            }
-            this.prevH = h;
-
-            if (animate) {
-                th.busy = true;
-                o.animate({height: h}, {
-                    duration: opt.animate,
-                    easing: ($.easing ? opt.easing : null),
-                    overflow: null,
-                    inline: true, // this option isn't jquery's. I added it by myself, see above
-                    complete: function(){
-                        // safari/chrome fix
-                        // somehow textarea turns to overflow:scroll after animation
-                        // i counldn't find it in jquery fx :(, so it looks like some bug
-                        if (!noHidden) {
-                            o.css('overflow', 'hidden');
-                        }
-                        // but if we still need to change overflow (due to opt.max option)
-                        // we have to invoke focus() event, otherwise browser will loose cursor
-                        if (!noFocus && updateMode != 'restore') {
-                            o.focus();
-                        }
-                        if (updateMode == 'growback') {
-                            dom.scrollTop = dom.scrollHeight;
-                        }
-                        th.busy = false;
-                        th.triggerEvents(updateMode);
-                    },
-                    queue: false
-                });
-            } else {
-                dom.style.height = h+'px';
-                this.triggerEvents(updateMode);
-            }
-        },
-
-        getDummyHeight: function() {
-            var val = this.o.val();
-            var h = 0;
-            var sr = this.sizeRelated;
-            var add = "\n111\n111";
-
-            // Safari has some defect with double new line symbol at the end
-            // It inserts additional new line even if you have only one
-            // But that't not the point :)
-            // Another question is how much pixels to keep at the bottom of textarea.
-            // We'll kill many rabbits at the same time by adding two new lines at the end
-            // (but if we have font-size and line-height defined, we'll add two line-heights)
-            if ($.browser.safari) {
-                val = val.substring(0, val.length-1); // safari has an additional new line ;(
-            }
-
-            if (!sr.lh || !sr.fs) {
-                val += add;
-            }
-
-            this.dummy.val(val);
-
-            // IE requires to change height value in order to recalculate scrollHeight.
-            // otherwise it stops recalculating scrollHeight after some magical number of pixels
-            if ($.browser.msie) {
-                this.dummy[0].style.height = this.dummy[0].scrollHeight+'px';
-            }
-
-            h = this.dummy[0].scrollHeight;
-
-            // if line-height is greater than font-size we'll add line-height + font-size
-            // otherwise font-size * 2
-            // there is no special logic in this behavior, it's been developed from visual testing
-            if (sr.lh && sr.fs) {
-                h += sr.lh > sr.fs ? sr.lh+sr.fs :  sr.fs * 2;
-            }
-
-            // now we have to minimize dummy back, or we'll get wrong scrollHeight next time
-            //if ($.browser.msie) {
-            //    this.dummy[0].style.height = '20px'; // random number
-            //}
-
-            return h;
-        },
-
-        convertHeight: function(h, to) {
-            var sr = this.sizeRelated, mod = (to=='inner' ? -1 : 1), bm = $.support.boxModel;
-            // what we get here in 'h' is scrollHeight value.
-            // so we need to subtract paddings not because of boxModel,
-            // but only if browser includes them to the scroll height (which is not defined by box model)
-            return h
-                + (bm ? sr.bt : 0) * mod
-                + (bm ? sr.bb : 0) * mod
-                + (bm ? sr.pt : 0) * mod
-                + (bm ? sr.pb : 0) * mod;
-        }
-
-    };
-
-    return that;
-})();
-
-/**
- * The growfield function. This will make a textarea a growing text area.
- *
- * @param {Object} options - See API for details on possible paramaters.
- */
-$.fn.growfield = function(options) {
-    // enable/disable is same thing as true/false
-    switch(options){
-        case 'enable':
-            options = true;
-            break;
-        case 'disable':
-            options = false;
-            break;
-    }
-
-    // we need to know what was passed as the options
-    var tp = typeof options;
-
-    // These variables are used to reduce string comparisons
-    // happening over and over.
-    var conditions = {
-        bool: tp == 'boolean',
-        string: tp == 'string',
-        object: tp == 'object',
-        restart: options == 'restart',
-        destroy: options == 'destroy'
-    };
-
-    // If the type of the options is a string
-    // and is not one of the pre-defined ones, then
-    // options is a preset.
-    if(conditions.string && !conditions.destroy && !conditions.restart){
-        options = $.fn.growfield.presets[options];
-        // change to new conditions
-        conditions.string = false;
-        conditions.object = true;
-    }
-
-    // completely remove growfield from the dom elements
-    if (conditions.destroy) {
-        this.each(function() {
-            var self = $(this);
-            var gf = self.data('growfield');
-            if (gf !== undefined) {
-                gf.unbind();
-                self.removeData('growfield');
-            }
-        });
-    }
-    // Apply growfield
-    else {
-        var textareaRegex = /textarea/i;
-        this.each(function() {
-            // only deal with textareas which are not dummy fields.
-            if (textareaRegex.test(this.tagName) && !$(this).hasClass('growfieldDummy')) {
-                var o = $(this);
-                var gf = o.data('growfield');
-                // Create the new options
-                if (gf === undefined) {
-                    gf = $.growfield(this,options);
-                    o.data('growfield', gf);
-
-                    // Bind only if the options is not a boolean
-                    // or is not "false". Because options = a false boolean
-                    // indicates intial bind should not happen.
-                    if(!conditions.bool || options){
-                        gf.bind();
-                    }
-                }
-                // Otherwise apply actions based on the options provided
-                else {
-                    // If new options provided, set them
-                    if(conditions.object && options) {
-                        $.extend(gf.opt,options);
-                    }
-                    // If toggling enable/disable then do it
-                    else if (conditions.bool) {
-                        gf.toggle(options);
-                    }
-                    // If restarting, restart
-                    else if (conditions.restart) {
-                        gf.unbind();
-                        gf.bind();
-                    }
-                }
-            }
-        });
-    }
-
-    return this;
-};
-
-/**
- * These are the default options to use, unless specified when invoking growfield.
- */
-$.fn.growfield.defaults ={
-    // Should the growfield automatically expand?
-    auto: true,
-    // The animation speed for expanding (false = off)
-    animate: 100,
-    // The easiny function to use, if the jquery.easing plugin is not present during
-    // execution, this will always be treated as null regardless of the set value
-    easing: null,
-    // The minimum height (defaults to CSS min-height, or the current height of the element)
-    min: false,
-    // The maximum height (defaults to CSS max-height, or unlimited)
-    max: false,
-    // Should the element restore to it's original size after focus is lost?
-    restore: false,
-    // How many pixels to expand when the user is about to have to scroll. Defaults to 1 line.
-    step: false
-};
-
-/**
- * These are presets. The presets are indexed by name containing different preset
- * option objects. When growfield is invoked with the preset's name, that options object
- * is loaded without having to be specified each time.
- */
-$.fn.growfield.presets = {};
-
-})(jQuery);
-