Renamed jquery.growfield.js to jquery-growfield.js.
Reviewed by: Lennard de Rijk
--- /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);
-