-
Notifications
You must be signed in to change notification settings - Fork 64
/
autogrow.js
121 lines (113 loc) · 5.64 KB
/
autogrow.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
;(function($){
//pass in just the context as a $(obj) or a settings JS object
$.fn.autogrow = function(opts) {
var that = $(this).css({overflow: 'hidden', resize: 'none'}) //prevent scrollies
, selector = that.selector
, defaults = {
context: $(document) //what to wire events to
, animate: true //if you want the size change to animate
, speed: 200 //speed of animation
, fixMinHeight: true //if you don't want the box to shrink below its initial size
, cloneClass: 'autogrowclone' //helper CSS class for clone if you need to add special rules
, onInitialize: false //resizes the textareas when the plugin is initialized
}
;
opts = $.isPlainObject(opts) ? opts : {context: opts ? opts : $(document)};
opts = $.extend({}, defaults, opts);
that.each(function(i, elem){
var min, clone;
elem = $(elem);
//if the element is "invisible", we get an incorrect height value
//to get correct value, clone and append to the body.
if (elem.is(':visible') || parseInt(elem.css('height'), 10) > 0) {
min = parseInt(elem.css('height'), 10) || elem.innerHeight();
} else {
clone = elem.clone()
.addClass(opts.cloneClass)
.val(elem.val())
.css({
position: 'absolute'
, visibility: 'hidden'
, display: 'block'
})
;
$('body').append(clone);
min = clone.innerHeight();
clone.remove();
}
if (opts.fixMinHeight) {
elem.data('autogrow-start-height', min); //set min height
}
elem.css('height', min);
if (opts.onInitialize && elem.length) {
resize.call(elem[0]);
}
});
opts.context
.on('keyup paste', selector, resize)
;
function resize (e){
var box = $(this)
, oldHeight = box.innerHeight()
, newHeight = this.scrollHeight
, minHeight = box.data('autogrow-start-height') || 0
, clone
;
if (oldHeight < newHeight) { //user is typing
this.scrollTop = 0; //try to reduce the top of the content hiding for a second
if(opts.animate) {
box.stop().animate({height: newHeight}, {duration: opts.speed, complete: notifyGrown});
} else {
box.innerHeight(newHeight);
notifyGrown();
}
} else if (!e || e.which == 8 || e.which == 46 || (e.ctrlKey && e.which == 88)) { //user is deleting, backspacing, or cutting
if (oldHeight > minHeight) { //shrink!
//this cloning part is not particularly necessary. however, it helps with animation
//since the only way to cleanly calculate where to shrink the box to is to incrementally
//reduce the height of the box until the $.innerHeight() and the scrollHeight differ.
//doing this on an exact clone to figure out the height first and then applying it to the
//actual box makes it look cleaner to the user
clone = box.clone()
//add clone class for extra css rules
.addClass(opts.cloneClass)
//make "invisible", remove height restriction potentially imposed by existing CSS
.css({position: 'absolute', zIndex:-10, height: ''})
//populate with content for consistent measuring
.val(box.val())
;
box.after(clone); //append as close to the box as possible for best CSS matching for clone
do { //reduce height until they don't match
newHeight = clone[0].scrollHeight - 1;
clone.innerHeight(newHeight);
} while (newHeight === clone[0].scrollHeight);
newHeight++; //adding one back eliminates a wiggle on deletion
clone.remove();
box.focus(); // Fix issue with Chrome losing focus from the textarea.
//if user selects all and deletes or holds down delete til beginning
//user could get here and shrink whole box
newHeight < minHeight && (newHeight = minHeight);
if(oldHeight > newHeight) {
if(opts.animate) {
box.stop().animate({height: newHeight}, {duration: opts.speed, complete: notifyShrunk});
} else {
box.innerHeight(newHeight);
notifyShrunk();
}
}
} else { //just set to the minHeight
box.innerHeight(minHeight);
}
}
}
// Trigger event to indicate a textarea has grown.
function notifyGrown() {
opts.context.trigger('autogrow:grow');
}
// Trigger event to indicate a textarea has shrunk.
function notifyShrunk() {
opts.context.trigger('autogrow:shrink');
}
return that;
}
})(jQuery);