best_in_place 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
  });