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 +9 -3
- data/.rspec +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -0
- data/README.md +3 -1
- data/Rakefile +6 -0
- data/best_in_place.gemspec +5 -0
- data/lib/best_in_place.rb +3 -40
- data/lib/best_in_place/helper.rb +52 -0
- data/lib/best_in_place/test_helpers.rb +26 -0
- data/lib/best_in_place/version.rb +2 -2
- data/public/javascripts/best_in_place.js +148 -125
- data/spec/helpers/best_in_place_spec.rb +229 -0
- data/spec/integration/double_init_spec.rb +32 -0
- data/spec/integration/js_spec.rb +165 -0
- data/spec/spec_helper.rb +23 -0
- data/test_app/Gemfile +1 -25
- data/test_app/Gemfile.lock +57 -50
- data/test_app/app/controllers/users_controller.rb +15 -1
- data/test_app/app/views/users/double_init.html.erb +65 -0
- data/test_app/app/views/users/show.html.erb +15 -15
- data/test_app/config/environments/development.rb +0 -1
- data/test_app/config/initializers/countries.rb +1 -1
- data/test_app/config/routes.rb +6 -56
- data/test_app/db/schema.rb +1 -0
- data/test_app/db/seeds.rb +1 -0
- data/test_app/public/javascripts/application.js +2 -2
- data/test_app/public/javascripts/best_in_place.js +146 -125
- data/test_app/public/stylesheets/style.css +17 -17
- metadata +79 -56
- data/Gemfile.lock +0 -78
- data/test_app/.gitignore +0 -4
- data/test_app/public/javascripts/jquery.rest_in_place.js +0 -254
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
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
data/best_in_place.gemspec
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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) + '"
|
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
|
-
*
|
328
|
-
*
|
329
|
-
*
|
330
|
-
*
|
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
|
-
*
|
333
|
-
*
|
334
|
-
*
|
355
|
+
* @author Jan Jarfalk
|
356
|
+
* @author-email jan.jarfalk@unwrongest.com
|
357
|
+
* @author-website http://www.unwrongest.com
|
335
358
|
*
|
336
|
-
*
|
359
|
+
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
337
360
|
*/
|
338
361
|
|
339
362
|
(function(jQuery){
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
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+' ');
|
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,'&').replace(/ /g, ' ').replace(/<|>/g, '>').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+' ' != twinContent){
|
448
427
|
|
449
|
-
|
450
|
-
|
428
|
+
// Add an extra white space so new rows are added when you are at the end of a row.
|
429
|
+
$twin.html(textareaContent+' ');
|
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
|
});
|