best_in_place 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1,5 +1,11 @@
1
1
  .DS_Store
2
- testapp/log
3
- testapp/config/database.yml
4
- .bundle/*
2
+ .bundle
5
3
  pkg/*
4
+
5
+ Gemfile.lock
6
+ test_app/.bundle
7
+ test_app/db/*.sqlite3
8
+ test_app/log/*.log
9
+ test_app/tmp/**/*
10
+
11
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - ree
4
+ - 1.9.2
5
+ - 1.9.3
6
+
7
+ env: "RAILS_ENV=test DISPLAY=:99.0"
8
+
9
+ before_script:
10
+ - "sh -c 'cd test_app && bundle && bundle exec rake db:drop db:migrate'"
11
+ - "sh -e /etc/init.d/xvfb start"
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in best_in_place.gemspec
4
4
  gemspec
5
+
6
+ gem 'sqlite3-ruby', :require => 'sqlite3'
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Best In Place
2
+ [![Build Status](https://secure.travis-ci.org/bernat/best_in_place.png)](http://travis-ci.org/bernat/best_in_place)
2
3
  **The Unobtrusive in Place editing solution**
3
4
 
4
5
 
@@ -47,6 +48,7 @@ Options:
47
48
  If not defined it will show *"-"*.
48
49
  - **:activator**: Is the DOM object that can activate the field. If not defined the user will making editable by clicking on it.
49
50
  - **:sanitize**: True by default. If set to false the input/textarea will accept html tags.
51
+ - **:html_args**: Hash of html arguments, such as maxlength, default-value etc.
50
52
 
51
53
 
52
54
  I created a [test_app](https://github.com/bernat/best_in_place/tree/master/test_app) and a running demo in heroku to test the features.
@@ -176,4 +178,4 @@ If the script is used with the Rails Gem no html tags will be allowed unless the
176
178
 
177
179
  Code by [Bernat Farrero](http://bernatfarrero.com) from [Itnig Web Services](http://itnig.net) (it was based on the [original project](http://github.com/janv/rest_in_place/) of Jan Varwig) and released under [MIT license](http://www.opensource.org/licenses/mit-license.php).
178
180
 
179
- Many thanks to the contributors: [Roger Campos](http://github.com/rogercampos) and [Jack Senechal](https://github.com/jacksenechal)
181
+ Many thanks to the contributors: [Roger Campos](http://github.com/rogercampos) and [Jack Senechal](https://github.com/jacksenechal)
data/Rakefile CHANGED
@@ -1,2 +1,8 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
@@ -18,5 +18,10 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
+
21
22
  s.add_dependency "rails", "~> 3.0.0"
23
+
24
+ s.add_development_dependency "rspec-rails", "~> 2.7.0"
25
+ s.add_development_dependency "nokogiri", ">= 1.5.0"
26
+ s.add_development_dependency "capybara", ">= 1.0.1"
22
27
  end
data/lib/best_in_place.rb CHANGED
@@ -1,44 +1,7 @@
1
+ require "best_in_place/helper"
2
+
1
3
  module BestInPlace
2
- module BestInPlaceHelpers
3
- def best_in_place(object, field, opts = {})
4
- opts[:type] ||= :input
5
- opts[:collection] ||= []
6
- field = field.to_s
7
- value = object.send(field).blank? ? "" : object.send(field)
8
- collection = nil
9
- if opts[:type] == :select && !opts[:collection].blank?
10
- v = object.send(field)
11
- value = Hash[opts[:collection]][!!(v =~ /^[0-9]+$/) ? v.to_i : v]
12
- collection = opts[:collection].to_json
13
- end
14
- if opts[:type] == :checkbox
15
- fieldValue = !!object.send(field)
16
- if opts[:collection].blank? || opts[:collection].size != 2
17
- opts[:collection] = ["No", "Yes"]
18
- end
19
- value = fieldValue ? opts[:collection][1] : opts[:collection][0]
20
- collection = opts[:collection].to_json
21
- end
22
- out = "<span class='best_in_place'"
23
- out << " id='best_in_place_#{object.class.to_s.gsub("::", "_").underscore}_#{field}'"
24
- out << " data-url='#{opts[:path].blank? ? url_for(object).to_s : url_for(opts[:path])}'"
25
- out << " data-object='#{object.class.to_s.gsub("::", "_").underscore}'"
26
- out << " data-collection='#{collection}'" unless collection.blank?
27
- out << " data-attribute='#{field}'"
28
- out << " data-activator='#{opts[:activator]}'" unless opts[:activator].blank?
29
- out << " data-nil='#{opts[:nil].to_s}'" unless opts[:nil].blank?
30
- out << " data-type='#{opts[:type].to_s}'"
31
- if !opts[:sanitize].nil? && !opts[:sanitize]
32
- out << " data-sanitize='false'>"
33
- out << sanitize(value.to_s, :tags => %w(b i u s a strong em p h1 h2 h3 h4 h5 ul li ol hr pre span img), :attributes => %w(id class))
34
- else
35
- out << ">#{sanitize(value.to_s, :tags => nil, :attributes => nil)}"
36
- end
37
- out << "</span>"
38
- raw out
39
- end
40
- end
4
+ autoload :TestHelpers, "best_in_place/test_helpers"
41
5
  end
42
6
 
43
-
44
7
  ActionView::Base.send(:include, BestInPlace::BestInPlaceHelpers)
@@ -0,0 +1,52 @@
1
+ module BestInPlace
2
+ module BestInPlaceHelpers
3
+ def best_in_place(object, field, opts = {})
4
+ opts[:type] ||= :input
5
+ opts[:collection] ||= []
6
+ field = field.to_s
7
+ value = object.send(field).blank? ? "" : object.send(field)
8
+ collection = nil
9
+ if opts[:type] == :select && !opts[:collection].blank?
10
+ v = object.send(field)
11
+ value = Hash[opts[:collection]][!!(v =~ /^[0-9]+$/) ? v.to_i : v]
12
+ collection = opts[:collection].to_json
13
+ end
14
+ if opts[:type] == :checkbox
15
+ fieldValue = !!object.send(field)
16
+ if opts[:collection].blank? || opts[:collection].size != 2
17
+ opts[:collection] = ["No", "Yes"]
18
+ end
19
+ value = fieldValue ? opts[:collection][1] : opts[:collection][0]
20
+ collection = opts[:collection].to_json
21
+ end
22
+ out = "<span class='best_in_place'"
23
+ out << " id='best_in_place_#{object.class.to_s.gsub("::", "_").underscore}_#{field}'"
24
+ out << " data-url='#{opts[:path].blank? ? url_for(object).to_s : url_for(opts[:path])}'"
25
+ out << " data-object='#{object.class.to_s.gsub("::", "_").underscore}'"
26
+ out << " data-collection='#{collection}'" unless collection.blank?
27
+ out << " data-attribute='#{field}'"
28
+ out << " data-activator='#{opts[:activator]}'" unless opts[:activator].blank?
29
+ out << " data-nil='#{opts[:nil].to_s}'" unless opts[:nil].blank?
30
+ out << " data-type='#{opts[:type].to_s}'"
31
+ out << " data-inner-class='#{opts[:inner_class].to_s}'" if opts[:inner_class]
32
+ out << " data-html-attrs='#{opts[:html_attrs].to_json}'" unless opts[:html_attrs].blank?
33
+ if !opts[:sanitize].nil? && !opts[:sanitize]
34
+ out << " data-sanitize='false'>"
35
+ out << sanitize(value.to_s, :tags => %w(b i u s a strong em p h1 h2 h3 h4 h5 ul li ol hr pre span img br), :attributes => %w(id class href))
36
+ else
37
+ out << ">#{sanitize(value.to_s, :tags => nil, :attributes => nil)}"
38
+ end
39
+ out << "</span>"
40
+ raw out
41
+ end
42
+
43
+ def best_in_place_if(condition, object, field, opts={})
44
+ if condition
45
+ best_in_place(object, field, opts)
46
+ else
47
+ object.send field
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,26 @@
1
+ module BestInPlace
2
+ module TestHelpers
3
+ def bip_text(model, attr, new_value)
4
+ page.execute_script <<-JS
5
+ $("#best_in_place_#{model}_#{attr}").click();
6
+ $("#best_in_place_#{model}_#{attr} input[name='#{attr}']").val('#{new_value}');
7
+ $("#best_in_place_#{model}_#{attr} form").submit();
8
+ JS
9
+ end
10
+
11
+ def bip_bool(model, attr)
12
+ page.execute_script("$('#best_in_place_#{model}_#{attr}').click();")
13
+ end
14
+
15
+ def bip_select(model, attr, name)
16
+ page.execute_script <<-JS
17
+ (function() {
18
+ $("#best_in_place_#{model}_#{attr}").click();
19
+ var opt_value = $("#best_in_place_#{model}_#{attr} select option:contains('#{name}')").attr('value');
20
+ $("#best_in_place_#{model}_#{attr} select option[value='" + opt_value + "']").attr('selected', true);
21
+ $("#best_in_place_#{model}_#{attr} select").change();
22
+ })();
23
+ JS
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module BestInPlace
2
- VERSION = "0.1.9"
3
- end
2
+ VERSION = "0.2.0"
3
+ end
@@ -31,7 +31,7 @@ BestInPlaceEditor.prototype = {
31
31
  // Public Interface Functions //////////////////////////////////////////////
32
32
 
33
33
  activate : function() {
34
- var elem = this.isNil ? "" : this.element.html();
34
+ var elem = this.isNil ? "" : this.element.html();
35
35
  this.oldValue = elem;
36
36
  $(this.activator).unbind("click", this.clickHandler);
37
37
  this.activateForm();
@@ -69,7 +69,7 @@ BestInPlaceEditor.prototype = {
69
69
  } else if (this.formType == "checkbox") {
70
70
  editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
71
71
  } else {
72
- editor.element.html(this.getValue());
72
+ editor.element.html(this.getValue() != "" ? this.getValue() : this.nil);
73
73
  }
74
74
  },
75
75
 
@@ -89,6 +89,8 @@ BestInPlaceEditor.prototype = {
89
89
  self.objectName = self.objectName || jQuery(this).attr("data-object");
90
90
  self.attributeName = self.attributeName || jQuery(this).attr("data-attribute");
91
91
  self.nil = self.nil || jQuery(this).attr("data-nil");
92
+ self.inner_class = self.inner_class || jQuery(this).attr("data-inner-class");
93
+ self.html_attrs = self.html_attrs || jQuery(this).attr("data-html-attrs");
92
94
  });
93
95
 
94
96
  // Try Rails-id based if parents did not explicitly supply something
@@ -107,6 +109,8 @@ BestInPlaceEditor.prototype = {
107
109
  self.attributeName = self.element.attr("data-attribute") || self.attributeName;
108
110
  self.activator = self.element.attr("data-activator") || self.element;
109
111
  self.nil = self.element.attr("data-nil") || self.nil || "-";
112
+ self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
113
+ self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
110
114
 
111
115
  if (!self.element.attr("data-sanitize")) {
112
116
  self.sanitize = true;
@@ -160,7 +164,7 @@ BestInPlaceEditor.prototype = {
160
164
 
161
165
  if (csrf_param !== undefined && csrf_token !== undefined) {
162
166
  data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
163
- }
167
+ }
164
168
  return data;
165
169
  },
166
170
 
@@ -174,7 +178,9 @@ BestInPlaceEditor.prototype = {
174
178
 
175
179
  loadSuccessCallback : function(data) {
176
180
  this.element.html(data[this.objectName]);
177
- // Binding back after being clicked
181
+ this.element.trigger($.Event("ajax:success"), data);
182
+
183
+ // Binding back after being clicked
178
184
  $(this.activator).bind('click', {editor: this}, this.clickHandler);
179
185
  },
180
186
 
@@ -183,16 +189,25 @@ BestInPlaceEditor.prototype = {
183
189
 
184
190
  // Display all error messages from server side validation
185
191
  $.each(jQuery.parseJSON(request.responseText), function(index, value) {
192
+ if( typeof(value) == "object") {value = index + " " + value.toString(); }
186
193
  var container = $("<span class='flash-error'></span>").html(value);
187
194
  container.purr();
188
195
  });
189
196
 
190
- // Binding back after being clicked
197
+ // Binding back after being clicked
191
198
  $(this.activator).bind('click', {editor: this}, this.clickHandler);
192
199
  },
193
200
 
194
201
  clickHandler : function(event) {
195
202
  event.data.editor.activate();
203
+ },
204
+
205
+ setHtmlAttributes : function() {
206
+ var formField = this.element.find(this.formType);
207
+ var attrs = jQuery.parseJSON(this.html_attrs);
208
+ for(var key in attrs){
209
+ formField.attr(key, attrs[key]);
210
+ }
196
211
  }
197
212
  };
198
213
 
@@ -201,8 +216,13 @@ BestInPlaceEditor.forms = {
201
216
  "input" : {
202
217
  activateForm : function() {
203
218
  var output = '<form class="form_in_place" action="javascript:void(0)" style="display:inline;">';
204
- output += '<input type="text" value="' + this.sanitizeValue(this.oldValue) + '"></form>';
219
+ output += '<input type="text" name="'+ this.attributeName + '" value="' + this.sanitizeValue(this.oldValue) + '"';
220
+ if (this.inner_class != null) {
221
+ output += ' class="' + this.inner_class + '"';
222
+ }
223
+ output += '></form>'
205
224
  this.element.html(output);
225
+ this.setHtmlAttributes();
206
226
  this.element.find('input')[0].select();
207
227
  this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
208
228
  this.element.find("input").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
@@ -239,6 +259,7 @@ BestInPlaceEditor.forms = {
239
259
  });
240
260
  output += "</select></form>";
241
261
  this.element.html(output);
262
+ this.setHtmlAttributes();
242
263
  this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
243
264
  this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
244
265
  this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
@@ -263,6 +284,7 @@ BestInPlaceEditor.forms = {
263
284
  var newValue = Boolean(this.oldValue != this.values[1]);
264
285
  var output = newValue ? this.values[1] : this.values[0];
265
286
  this.element.html(output);
287
+ this.setHtmlAttributes();
266
288
  this.update();
267
289
  },
268
290
 
@@ -282,6 +304,7 @@ BestInPlaceEditor.forms = {
282
304
  output += this.sanitizeValue(this.oldValue);
283
305
  output += '</textarea></form>';
284
306
  this.element.html(output);
307
+ this.setHtmlAttributes();
285
308
 
286
309
  // set width and height of textarea
287
310
  jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
@@ -324,132 +347,132 @@ jQuery.fn.best_in_place = function() {
324
347
 
325
348
 
326
349
  /**
327
- * @name Elastic
328
- * @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
329
- * @version 1.6.5
330
- * @requires Jquery 1.2.6+
350
+ * @name Elastic
351
+ * @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
352
+ * @version 1.6.5
353
+ * @requires Jquery 1.2.6+
331
354
  *
332
- * @author Jan Jarfalk
333
- * @author-email jan.jarfalk@unwrongest.com
334
- * @author-website http://www.unwrongest.com
355
+ * @author Jan Jarfalk
356
+ * @author-email jan.jarfalk@unwrongest.com
357
+ * @author-website http://www.unwrongest.com
335
358
  *
336
- * @licens MIT License - http://www.opensource.org/licenses/mit-license.php
359
+ * @licens MIT License - http://www.opensource.org/licenses/mit-license.php
337
360
  */
338
361
 
339
362
  (function(jQuery){
340
- jQuery.fn.extend({
341
- elastic: function() {
342
- // We will create a div clone of the textarea
343
- // by copying these attributes from the textarea to the div.
344
- var mimics = [
345
- 'paddingTop',
346
- 'paddingRight',
347
- 'paddingBottom',
348
- 'paddingLeft',
349
- 'fontSize',
350
- 'lineHeight',
351
- 'fontFamily',
352
- 'width',
353
- 'fontWeight'];
354
-
355
- return this.each( function() {
356
-
357
- // Elastic only works on textareas
358
- if ( this.type != 'textarea' ) {
359
- return false;
360
- }
361
-
362
- var $textarea = jQuery(this),
363
- $twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
364
- lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
365
- minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
366
- maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
367
- goalheight = 0,
368
- i = 0;
369
-
370
- // Opera returns max-height of -1 if not set
371
- if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
372
-
373
- // Append the twin to the DOM
374
- // We are going to meassure the height of this, not the textarea.
375
- $twin.appendTo($textarea.parent());
376
-
377
- // Copy the essential styles (mimics) from the textarea to the twin
378
- var i = mimics.length;
379
- while(i--){
380
- $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
381
- }
382
-
383
-
384
- // Sets a given height and overflow state on the textarea
385
- function setHeightAndOverflow(height, overflow){
386
- curratedHeight = Math.floor(parseInt(height,10));
387
- if($textarea.height() != curratedHeight){
388
- $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
389
-
390
- }
391
- }
392
-
393
-
394
- // This function will update the height of the textarea if necessary
395
- function update() {
396
-
397
- // Get curated content from the textarea.
398
- var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/ /g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
399
-
400
- // Compare curated content with curated twin.
401
- var twinContent = $twin.html().replace(/<br>/ig,'<br />');
402
-
403
- if(textareaContent+'&nbsp;' != twinContent){
404
-
405
- // Add an extra white space so new rows are added when you are at the end of a row.
406
- $twin.html(textareaContent+'&nbsp;');
407
-
408
- // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
409
- if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
410
-
411
- var goalheight = $twin.height()+lineHeight;
412
- if(goalheight >= maxheight) {
413
- setHeightAndOverflow(maxheight,'auto');
414
- } else if(goalheight <= minheight) {
415
- setHeightAndOverflow(minheight,'hidden');
416
- } else {
417
- setHeightAndOverflow(goalheight,'hidden');
418
- }
419
-
420
- }
421
-
422
- }
423
-
424
- }
425
-
426
- // Hide scrollbars
427
- $textarea.css({'overflow':'hidden'});
428
-
429
- // Update textarea size on keyup, change, cut and paste
430
- $textarea.bind('keyup change cut paste', function(){
431
- update();
432
- });
433
-
434
- // Compact textarea on blur
435
- // Lets animate this....
436
- $textarea.bind('blur',function(){
437
- if($twin.height() < maxheight){
438
- if($twin.height() > minheight) {
439
- $textarea.height($twin.height());
440
- } else {
441
- $textarea.height(minheight);
442
- }
443
- }
444
- });
445
-
446
- // And this line is to catch the browser paste event
447
- $textarea.live('input paste',function(e){ setTimeout( update, 250); });
363
+ jQuery.fn.extend({
364
+ elastic: function() {
365
+ // We will create a div clone of the textarea
366
+ // by copying these attributes from the textarea to the div.
367
+ var mimics = [
368
+ 'paddingTop',
369
+ 'paddingRight',
370
+ 'paddingBottom',
371
+ 'paddingLeft',
372
+ 'fontSize',
373
+ 'lineHeight',
374
+ 'fontFamily',
375
+ 'width',
376
+ 'fontWeight'];
377
+
378
+ return this.each( function() {
379
+
380
+ // Elastic only works on textareas
381
+ if ( this.type != 'textarea' ) {
382
+ return false;
383
+ }
384
+
385
+ var $textarea = jQuery(this),
386
+ $twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
387
+ lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
388
+ minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
389
+ maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
390
+ goalheight = 0,
391
+ i = 0;
392
+
393
+ // Opera returns max-height of -1 if not set
394
+ if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
395
+
396
+ // Append the twin to the DOM
397
+ // We are going to meassure the height of this, not the textarea.
398
+ $twin.appendTo($textarea.parent());
399
+
400
+ // Copy the essential styles (mimics) from the textarea to the twin
401
+ var i = mimics.length;
402
+ while(i--){
403
+ $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
404
+ }
405
+
406
+
407
+ // Sets a given height and overflow state on the textarea
408
+ function setHeightAndOverflow(height, overflow){
409
+ curratedHeight = Math.floor(parseInt(height,10));
410
+ if($textarea.height() != curratedHeight){
411
+ $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
412
+
413
+ }
414
+ }
415
+
416
+
417
+ // This function will update the height of the textarea if necessary
418
+ function update() {
419
+
420
+ // Get curated content from the textarea.
421
+ var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/ /g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
422
+
423
+ // Compare curated content with curated twin.
424
+ var twinContent = $twin.html().replace(/<br>/ig,'<br />');
425
+
426
+ if(textareaContent+'&nbsp;' != twinContent){
448
427
 
449
- // Run update once when elastic is initialized
450
- update();
428
+ // Add an extra white space so new rows are added when you are at the end of a row.
429
+ $twin.html(textareaContent+'&nbsp;');
430
+
431
+ // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
432
+ if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
433
+
434
+ var goalheight = $twin.height()+lineHeight;
435
+ if(goalheight >= maxheight) {
436
+ setHeightAndOverflow(maxheight,'auto');
437
+ } else if(goalheight <= minheight) {
438
+ setHeightAndOverflow(minheight,'hidden');
439
+ } else {
440
+ setHeightAndOverflow(goalheight,'hidden');
441
+ }
442
+
443
+ }
444
+
445
+ }
446
+
447
+ }
451
448
 
452
- });
449
+ // Hide scrollbars
450
+ $textarea.css({'overflow':'hidden'});
451
+
452
+ // Update textarea size on keyup, change, cut and paste
453
+ $textarea.bind('keyup change cut paste', function(){
454
+ update();
455
+ });
456
+
457
+ // Compact textarea on blur
458
+ // Lets animate this....
459
+ $textarea.bind('blur',function(){
460
+ if($twin.height() < maxheight){
461
+ if($twin.height() > minheight) {
462
+ $textarea.height($twin.height());
463
+ } else {
464
+ $textarea.height(minheight);
465
+ }
466
+ }
467
+ });
468
+
469
+ // And this line is to catch the browser paste event
470
+ $textarea.live('input paste',function(e){ setTimeout( update, 250); });
471
+
472
+ // Run update once when elastic is initialized
473
+ update();
474
+
475
+ });
453
476
 
454
477
  }
455
478
  });