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 +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
|
+
[](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
|
});
|