Commit ec2272898489f98e8e7bafa76d2ea74547486300
1 parent
12208eb9
Exists in
master
and in
29 other branches
wall: correct textarea growing indefinetely
ActionItem3136
Showing
4 changed files
with
98 additions
and
11 deletions
Show diff stats
app/views/layouts/_javascript.html.erb
@@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
3 | 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox', | 3 | 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox', |
4 | 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', | 4 | 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', |
5 | 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', | 5 | 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', |
6 | -'add-and-join', 'report-abuse', 'catalog', 'manage-products', | 6 | +'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow', |
7 | 'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', :cache => 'cache/application' %> | 7 | 'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', :cache => 'cache/application' %> |
8 | 8 | ||
9 | <% language = FastGettext.locale %> | 9 | <% language = FastGettext.locale %> |
app/views/profile/_profile_wall.html.erb
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <div id='leave_scrap'> | 2 | <div id='leave_scrap'> |
3 | <%= flash[:error] %> | 3 | <%= flash[:error] %> |
4 | <%= form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_activities', :success => "$('leave_scrap_content').value=''", :complete => "jQuery('#leave_scrap_form').removeClass('loading').find('*').attr('disabled', false)", :loading => "jQuery('#leave_scrap_form').addClass('loading').find('*').attr('disabled', true)", :html => {:id => 'leave_scrap_form' } do %> | 4 | <%= form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_activities', :success => "$('leave_scrap_content').value=''", :complete => "jQuery('#leave_scrap_form').removeClass('loading').find('*').attr('disabled', false)", :loading => "jQuery('#leave_scrap_form').addClass('loading').find('*').attr('disabled', true)", :html => {:id => 'leave_scrap_form' } do %> |
5 | - <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :cols => 50, :rows => 2 %> | 5 | + <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :cols => 50, :rows => 2, :class => 'autogrow' %> |
6 | <%= submit_button :new, _('Share') %> | 6 | <%= submit_button :new, _('Share') %> |
7 | <% end %> | 7 | <% end %> |
8 | </div> | 8 | </div> |
public/javascripts/application.js
@@ -681,7 +681,6 @@ function hide_and_show(hide_elements, show_elements) { | @@ -681,7 +681,6 @@ function hide_and_show(hide_elements, show_elements) { | ||
681 | 681 | ||
682 | function limited_text_area(textid, limit) { | 682 | function limited_text_area(textid, limit) { |
683 | var text = jQuery('#' + textid).val(); | 683 | var text = jQuery('#' + textid).val(); |
684 | - grow_text_area(textid); | ||
685 | var textlength = text.length; | 684 | var textlength = text.length; |
686 | jQuery('#' + textid + '_left span').html(limit - textlength); | 685 | jQuery('#' + textid + '_left span').html(limit - textlength); |
687 | if (textlength > limit) { | 686 | if (textlength > limit) { |
@@ -696,14 +695,9 @@ function limited_text_area(textid, limit) { | @@ -696,14 +695,9 @@ function limited_text_area(textid, limit) { | ||
696 | } | 695 | } |
697 | } | 696 | } |
698 | 697 | ||
699 | -function grow_text_area(textid) { | ||
700 | - var height = jQuery('#' + textid).attr('scrollHeight'); | ||
701 | - if (jQuery.browser.webkit) { | ||
702 | - height -= parseInt(jQuery('#' + textid).css('padding-top')) + | ||
703 | - parseInt(jQuery('#' + textid).css('padding-bottom')); | ||
704 | - } | ||
705 | - jQuery('#' + textid).css('height', height + 'px'); | ||
706 | -} | 698 | +jQuery(function($) { |
699 | + $('.autogrow').autogrow(); | ||
700 | +}); | ||
707 | 701 | ||
708 | jQuery(function($) { | 702 | jQuery(function($) { |
709 | $('a').each(function() { | 703 | $('a').each(function() { |
@@ -0,0 +1,93 @@ | @@ -0,0 +1,93 @@ | ||
1 | +;(function($){ | ||
2 | + //pass in just the context as a $(obj) or a settings JS object | ||
3 | + $.fn.autogrow = function(opts) { | ||
4 | + var that = $(this).css({overflow: 'hidden', resize: 'none'}) //prevent scrollies | ||
5 | + , selector = that.selector | ||
6 | + , defaults = { | ||
7 | + context: $(document) //what to wire events to | ||
8 | + , animate: true //if you want the size change to animate | ||
9 | + , speed: 200 //speed of animation | ||
10 | + , fixMinHeight: true //if you don't want the box to shrink below its initial size | ||
11 | + , cloneClass: 'autogrowclone' //helper CSS class for clone if you need to add special rules | ||
12 | + , onInitialize: false //resizes the textareas when the plugin is initialized | ||
13 | + } | ||
14 | + ; | ||
15 | + opts = $.isPlainObject(opts) ? opts : {context: opts ? opts : $(document)}; | ||
16 | + opts = $.extend({}, defaults, opts); | ||
17 | + that.each(function(i, elem){ | ||
18 | + var min, clone; | ||
19 | + elem = $(elem); | ||
20 | + //if the element is "invisible", we get an incorrect height value | ||
21 | + //to get correct value, clone and append to the body. | ||
22 | + if (elem.is(':visible') || parseInt(elem.css('height'), 10) > 0) { | ||
23 | + min = parseInt(elem.css('height'), 10) || elem.innerHeight(); | ||
24 | + } else { | ||
25 | + clone = elem.clone() | ||
26 | + .addClass(opts.cloneClass) | ||
27 | + .val(elem.val()) | ||
28 | + .css({ | ||
29 | + position: 'absolute' | ||
30 | + , visibility: 'hidden' | ||
31 | + , display: 'block' | ||
32 | + }) | ||
33 | + ; | ||
34 | + $('body').append(clone); | ||
35 | + min = clone.innerHeight(); | ||
36 | + clone.remove(); | ||
37 | + } | ||
38 | + if (opts.fixMinHeight) { | ||
39 | + elem.data('autogrow-start-height', min); //set min height | ||
40 | + } | ||
41 | + elem.css('height', min); | ||
42 | + | ||
43 | + if (opts.onInitialize) { | ||
44 | + resize.call(elem); | ||
45 | + } | ||
46 | + }); | ||
47 | + opts.context | ||
48 | + .on('keyup paste', selector, resize) | ||
49 | + ; | ||
50 | + | ||
51 | + function resize (e){ | ||
52 | + var box = $(this) | ||
53 | + , oldHeight = box.innerHeight() | ||
54 | + , newHeight = this.scrollHeight | ||
55 | + , minHeight = box.data('autogrow-start-height') || 0 | ||
56 | + , clone | ||
57 | + ; | ||
58 | + if (oldHeight < newHeight) { //user is typing | ||
59 | + this.scrollTop = 0; //try to reduce the top of the content hiding for a second | ||
60 | + opts.animate ? box.stop().animate({height: newHeight}, opts.speed) : box.innerHeight(newHeight); | ||
61 | + } else if (!e || e.which == 8 || e.which == 46 || (e.ctrlKey && e.which == 88)) { //user is deleting, backspacing, or cutting | ||
62 | + if (oldHeight > minHeight) { //shrink! | ||
63 | + //this cloning part is not particularly necessary. however, it helps with animation | ||
64 | + //since the only way to cleanly calculate where to shrink the box to is to incrementally | ||
65 | + //reduce the height of the box until the $.innerHeight() and the scrollHeight differ. | ||
66 | + //doing this on an exact clone to figure out the height first and then applying it to the | ||
67 | + //actual box makes it look cleaner to the user | ||
68 | + clone = box.clone() | ||
69 | + .addClass(opts.cloneClass) //add clone class for extra css rules | ||
70 | + .css({position: 'absolute', zIndex:-10}) //make "invisible" | ||
71 | + .val(box.val()) //populate with content for consistent measuring | ||
72 | + ; | ||
73 | + box.after(clone); //append as close to the box as possible for best CSS matching for clone | ||
74 | + do { //reduce height until they don't match | ||
75 | + newHeight = clone[0].scrollHeight - 1; | ||
76 | + clone.innerHeight(newHeight); | ||
77 | + } while (newHeight === clone[0].scrollHeight); | ||
78 | + newHeight++; //adding one back eliminates a wiggle on deletion | ||
79 | + clone.remove(); | ||
80 | + box.focus(); // Fix issue with Chrome losing focus from the textarea. | ||
81 | + | ||
82 | + //if user selects all and deletes or holds down delete til beginning | ||
83 | + //user could get here and shrink whole box | ||
84 | + newHeight < minHeight && (newHeight = minHeight); | ||
85 | + oldHeight > newHeight && opts.animate ? box.stop().animate({height: newHeight}, opts.speed) : box.innerHeight(newHeight); | ||
86 | + } else { //just set to the minHeight | ||
87 | + box.innerHeight(minHeight); | ||
88 | + } | ||
89 | + } | ||
90 | + } | ||
91 | + return that; | ||
92 | + } | ||
93 | +})(jQuery); |