best_in_place 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +7 -6
- data/lib/best_in_place.rb +3 -3
- data/lib/best_in_place/version.rb +1 -1
- data/public/javascripts/best_in_place.js +176 -7
- data/test_app/Gemfile.lock +1 -1
- data/test_app/app/controllers/users_controller.rb +0 -5
- data/test_app/app/views/users/_form.html.erb +8 -4
- data/test_app/config/routes.rb +1 -5
- data/test_app/public/javascripts/application.js +2 -2
- data/test_app/public/javascripts/best_in_place.js +176 -7
- data/test_app/public/stylesheets/style.css +9 -6
- data/test_app/test/fixtures/users.yml +12 -12
- metadata +4 -4
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -20,10 +20,11 @@ The editor works by PUTting the updated value to the server and GETting the upda
|
|
20
20
|
- Compatible with **textarea**
|
21
21
|
- Compatible with **select** dropdown with custom collections
|
22
22
|
- Compatible with custom boolean values (same usage of **checkboxes**)
|
23
|
-
- Sanitize HTML and trim spaces of user's input
|
23
|
+
- Sanitize HTML and trim spaces of user's input on user's choice
|
24
24
|
- Displays server-side **validation** errors
|
25
25
|
- Allows external activator
|
26
|
-
- ESC key destroys changes
|
26
|
+
- ESC key destroys changes (requires user confirmation)
|
27
|
+
- Autogrowing textarea
|
27
28
|
|
28
29
|
---
|
29
30
|
|
@@ -122,7 +123,7 @@ When the user tries to introduce invalid data, the error messages defined in the
|
|
122
123
|
It works by simply copying and loading the files from the folder **/public/javascripts** to your application and loading them in your layouts
|
123
124
|
in the following order:
|
124
125
|
|
125
|
-
- jquery-1.4.
|
126
|
+
- jquery-1.4.4.js
|
126
127
|
- jquery.purr.js
|
127
128
|
- **best_in_place.js**
|
128
129
|
|
@@ -169,10 +170,10 @@ If the script is used with the Rails Gem no html tags will be allowed unless the
|
|
169
170
|
fixing bug with objects inside namespaces, adding feature for passing an external activator handler as param. Adding feature
|
170
171
|
of key ESCAPE for destroying changes before they are made permanent (in inputs and textarea).
|
171
172
|
- v.0.1.6-0.1.7 Avoiding request when the input is not modified and allowing the user to not sanitize input data.
|
172
|
-
- v.0.1.8 jslint compliant, sanitizing tags in the gem, getting right csrf params
|
173
|
+
- v.0.1.8 jslint compliant, sanitizing tags in the gem, getting right csrf params, controlling size of textarea (elastic script, for autogrowing textarea)
|
173
174
|
|
174
175
|
##Authors, License and Stuff
|
175
176
|
|
176
|
-
Code by [Bernat Farrero](http://bernatfarrero.com) (based on the [original project](http://github.com/janv/rest_in_place/) of Jan Varwig) and released under [MIT](http://www.opensource.org/licenses/mit-license.php).
|
177
|
+
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).
|
177
178
|
|
178
|
-
Many thanks to the contributors:
|
179
|
+
Many thanks to the contributors: [Roger Campos](http://github.com/rogercampos) and [Jack Senechal](https://github.com/jacksenechal)
|
data/lib/best_in_place.rb
CHANGED
@@ -3,9 +3,8 @@ module BestInPlace
|
|
3
3
|
def best_in_place(object, field, opts = {})
|
4
4
|
opts[:type] ||= :input
|
5
5
|
opts[:collection] ||= []
|
6
|
-
opts[:nil] ||= "-"
|
7
6
|
field = field.to_s
|
8
|
-
value = object.send(field).blank? ?
|
7
|
+
value = object.send(field).blank? ? "" : object.send(field)
|
9
8
|
collection = nil
|
10
9
|
if opts[:type] == :select && !opts[:collection].blank?
|
11
10
|
v = object.send(field)
|
@@ -27,12 +26,13 @@ module BestInPlace
|
|
27
26
|
out << " data-collection='#{collection}'" unless collection.blank?
|
28
27
|
out << " data-attribute='#{field}'"
|
29
28
|
out << " data-activator='#{opts[:activator]}'" unless opts[:activator].blank?
|
29
|
+
out << " data-nil='#{opts[:nil].to_s}'" unless opts[:nil].blank?
|
30
30
|
out << " data-type='#{opts[:type].to_s}'"
|
31
31
|
if !opts[:sanitize].nil? && !opts[:sanitize]
|
32
32
|
out << " data-sanitize='false'>"
|
33
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
34
|
else
|
35
|
-
out << ">#{sanitize(value, :tags => nil, :attributes => nil)}"
|
35
|
+
out << ">#{sanitize(value.to_s, :tags => nil, :attributes => nil)}"
|
36
36
|
end
|
37
37
|
out << "</span>"
|
38
38
|
raw out
|
@@ -23,6 +23,7 @@ function BestInPlaceEditor(e) {
|
|
23
23
|
this.element = jQuery(e);
|
24
24
|
this.initOptions();
|
25
25
|
this.bindForm();
|
26
|
+
this.initNil();
|
26
27
|
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
27
28
|
}
|
28
29
|
|
@@ -30,14 +31,15 @@ BestInPlaceEditor.prototype = {
|
|
30
31
|
// Public Interface Functions //////////////////////////////////////////////
|
31
32
|
|
32
33
|
activate : function() {
|
33
|
-
var elem = this.element.html();
|
34
|
+
var elem = this.isNil ? "" : this.element.html();
|
34
35
|
this.oldValue = elem;
|
35
36
|
$(this.activator).unbind("click", this.clickHandler);
|
36
37
|
this.activateForm();
|
37
38
|
},
|
38
39
|
|
39
40
|
abort : function() {
|
40
|
-
this.element.html(this.
|
41
|
+
if (this.isNil) this.element.html(this.nil);
|
42
|
+
else this.element.html(this.oldValue);
|
41
43
|
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
42
44
|
},
|
43
45
|
|
@@ -45,10 +47,10 @@ BestInPlaceEditor.prototype = {
|
|
45
47
|
var editor = this;
|
46
48
|
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
47
49
|
{ // Avoid request if no change is made
|
48
|
-
|
49
|
-
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
50
|
+
this.abort();
|
50
51
|
return true;
|
51
52
|
}
|
53
|
+
this.isNil = false;
|
52
54
|
editor.ajax({
|
53
55
|
"type" : "post",
|
54
56
|
"dataType" : "text",
|
@@ -86,6 +88,7 @@ BestInPlaceEditor.prototype = {
|
|
86
88
|
self.formType = self.formType || jQuery(this).attr("data-type");
|
87
89
|
self.objectName = self.objectName || jQuery(this).attr("data-object");
|
88
90
|
self.attributeName = self.attributeName || jQuery(this).attr("data-attribute");
|
91
|
+
self.nil = self.nil || jQuery(this).attr("data-nil");
|
89
92
|
});
|
90
93
|
|
91
94
|
// Try Rails-id based if parents did not explicitly supply something
|
@@ -103,6 +106,7 @@ BestInPlaceEditor.prototype = {
|
|
103
106
|
self.objectName = self.element.attr("data-object") || self.objectName;
|
104
107
|
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
105
108
|
self.activator = self.element.attr("data-activator") || self.element;
|
109
|
+
self.nil = self.element.attr("data-nil") || self.nil || "-";
|
106
110
|
|
107
111
|
if (!self.element.attr("data-sanitize")) {
|
108
112
|
self.sanitize = true;
|
@@ -122,6 +126,14 @@ BestInPlaceEditor.prototype = {
|
|
122
126
|
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
123
127
|
},
|
124
128
|
|
129
|
+
initNil: function() {
|
130
|
+
if (this.element.html() == "")
|
131
|
+
{
|
132
|
+
this.isNil = true
|
133
|
+
this.element.html(this.nil)
|
134
|
+
}
|
135
|
+
},
|
136
|
+
|
125
137
|
getValue : function() {
|
126
138
|
alert("The form was not properly initialized. getValue is unbound");
|
127
139
|
},
|
@@ -228,6 +240,9 @@ BestInPlaceEditor.forms = {
|
|
228
240
|
output += "</select></form>";
|
229
241
|
this.element.html(output);
|
230
242
|
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
243
|
+
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
244
|
+
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
245
|
+
this.element.find("select")[0].focus();
|
231
246
|
},
|
232
247
|
|
233
248
|
getValue : function() {
|
@@ -236,6 +251,10 @@ BestInPlaceEditor.forms = {
|
|
236
251
|
|
237
252
|
blurHandler : function(event) {
|
238
253
|
event.data.editor.update();
|
254
|
+
},
|
255
|
+
|
256
|
+
keyupHandler : function(event) {
|
257
|
+
if (event.keyCode == 27) event.data.editor.abort();
|
239
258
|
}
|
240
259
|
},
|
241
260
|
|
@@ -254,11 +273,21 @@ BestInPlaceEditor.forms = {
|
|
254
273
|
|
255
274
|
"textarea" : {
|
256
275
|
activateForm : function() {
|
276
|
+
// grab width and height of text
|
277
|
+
width = this.element.css('width');
|
278
|
+
height = this.element.css('height');
|
279
|
+
|
280
|
+
// construct the form
|
257
281
|
var output = '<form action="javascript:void(0)" style="display:inline;"><textarea>';
|
258
282
|
output += this.sanitizeValue(this.oldValue);
|
259
283
|
output += '</textarea></form>';
|
260
284
|
this.element.html(output);
|
261
|
-
|
285
|
+
|
286
|
+
// set width and height of textarea
|
287
|
+
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
288
|
+
jQuery(this.element.find("textarea")[0]).elastic();
|
289
|
+
|
290
|
+
this.element.find("textarea")[0].focus();
|
262
291
|
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
263
292
|
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
264
293
|
},
|
@@ -273,7 +302,13 @@ BestInPlaceEditor.forms = {
|
|
273
302
|
|
274
303
|
keyupHandler : function(event) {
|
275
304
|
if (event.keyCode == 27) {
|
276
|
-
event.data.editor
|
305
|
+
BestInPlaceEditor.forms.textarea.abort(event.data.editor);
|
306
|
+
}
|
307
|
+
},
|
308
|
+
|
309
|
+
abort : function(editor) {
|
310
|
+
if (confirm("Are you sure you want to discard your changes?")) {
|
311
|
+
editor.abort();
|
277
312
|
}
|
278
313
|
}
|
279
314
|
}
|
@@ -284,4 +319,138 @@ jQuery.fn.best_in_place = function() {
|
|
284
319
|
jQuery(this).data('bestInPlaceEditor', new BestInPlaceEditor(this));
|
285
320
|
});
|
286
321
|
return this;
|
287
|
-
};
|
322
|
+
};
|
323
|
+
|
324
|
+
|
325
|
+
|
326
|
+
/**
|
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+
|
331
|
+
*
|
332
|
+
* @author Jan Jarfalk
|
333
|
+
* @author-email jan.jarfalk@unwrongest.com
|
334
|
+
* @author-website http://www.unwrongest.com
|
335
|
+
*
|
336
|
+
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
337
|
+
*/
|
338
|
+
|
339
|
+
(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,'&').replace(/ /g, ' ').replace(/<|>/g, '>').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+' ' != 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+' ');
|
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); });
|
448
|
+
|
449
|
+
// Run update once when elastic is initialized
|
450
|
+
update();
|
451
|
+
|
452
|
+
});
|
453
|
+
|
454
|
+
}
|
455
|
+
});
|
456
|
+
})(jQuery);
|
data/test_app/Gemfile.lock
CHANGED
@@ -33,10 +33,14 @@
|
|
33
33
|
</div>
|
34
34
|
<div class="field">
|
35
35
|
<%= f.label :country %><br />
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
<%=
|
37
|
+
f.select :country, COUNTRIES.map {|c|
|
38
|
+
v0 = c[0]
|
39
|
+
c[0] = c[1]
|
40
|
+
c[1] = v0
|
41
|
+
c
|
42
|
+
}
|
43
|
+
%>
|
40
44
|
<div class="actions">
|
41
45
|
<%= f.submit %>
|
42
46
|
</div>
|
data/test_app/config/routes.rb
CHANGED
@@ -3,13 +3,13 @@
|
|
3
3
|
|
4
4
|
$(document).ready(function() {
|
5
5
|
/* Activating Best In Place */
|
6
|
-
jQuery(".best_in_place").best_in_place()
|
6
|
+
jQuery(".best_in_place").best_in_place();
|
7
7
|
});
|
8
8
|
|
9
9
|
function inspect(obj, maxLevels, level)
|
10
10
|
{
|
11
11
|
var str = '', type, msg;
|
12
|
-
|
12
|
+
1
|
13
13
|
// Start Input Validations
|
14
14
|
// Don't touch, we start iterating at level zero
|
15
15
|
if(level == null) level = 0;
|
@@ -23,6 +23,7 @@ function BestInPlaceEditor(e) {
|
|
23
23
|
this.element = jQuery(e);
|
24
24
|
this.initOptions();
|
25
25
|
this.bindForm();
|
26
|
+
this.initNil();
|
26
27
|
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
27
28
|
}
|
28
29
|
|
@@ -30,14 +31,15 @@ BestInPlaceEditor.prototype = {
|
|
30
31
|
// Public Interface Functions //////////////////////////////////////////////
|
31
32
|
|
32
33
|
activate : function() {
|
33
|
-
var elem = this.element.html();
|
34
|
+
var elem = this.isNil ? "" : this.element.html();
|
34
35
|
this.oldValue = elem;
|
35
36
|
$(this.activator).unbind("click", this.clickHandler);
|
36
37
|
this.activateForm();
|
37
38
|
},
|
38
39
|
|
39
40
|
abort : function() {
|
40
|
-
this.element.html(this.
|
41
|
+
if (this.isNil) this.element.html(this.nil);
|
42
|
+
else this.element.html(this.oldValue);
|
41
43
|
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
42
44
|
},
|
43
45
|
|
@@ -45,10 +47,10 @@ BestInPlaceEditor.prototype = {
|
|
45
47
|
var editor = this;
|
46
48
|
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
47
49
|
{ // Avoid request if no change is made
|
48
|
-
|
49
|
-
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
50
|
+
this.abort();
|
50
51
|
return true;
|
51
52
|
}
|
53
|
+
this.isNil = false;
|
52
54
|
editor.ajax({
|
53
55
|
"type" : "post",
|
54
56
|
"dataType" : "text",
|
@@ -86,6 +88,7 @@ BestInPlaceEditor.prototype = {
|
|
86
88
|
self.formType = self.formType || jQuery(this).attr("data-type");
|
87
89
|
self.objectName = self.objectName || jQuery(this).attr("data-object");
|
88
90
|
self.attributeName = self.attributeName || jQuery(this).attr("data-attribute");
|
91
|
+
self.nil = self.nil || jQuery(this).attr("data-nil");
|
89
92
|
});
|
90
93
|
|
91
94
|
// Try Rails-id based if parents did not explicitly supply something
|
@@ -103,6 +106,7 @@ BestInPlaceEditor.prototype = {
|
|
103
106
|
self.objectName = self.element.attr("data-object") || self.objectName;
|
104
107
|
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
105
108
|
self.activator = self.element.attr("data-activator") || self.element;
|
109
|
+
self.nil = self.element.attr("data-nil") || self.nil || "-";
|
106
110
|
|
107
111
|
if (!self.element.attr("data-sanitize")) {
|
108
112
|
self.sanitize = true;
|
@@ -122,6 +126,14 @@ BestInPlaceEditor.prototype = {
|
|
122
126
|
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
123
127
|
},
|
124
128
|
|
129
|
+
initNil: function() {
|
130
|
+
if (this.element.html() == "")
|
131
|
+
{
|
132
|
+
this.isNil = true
|
133
|
+
this.element.html(this.nil)
|
134
|
+
}
|
135
|
+
},
|
136
|
+
|
125
137
|
getValue : function() {
|
126
138
|
alert("The form was not properly initialized. getValue is unbound");
|
127
139
|
},
|
@@ -228,6 +240,9 @@ BestInPlaceEditor.forms = {
|
|
228
240
|
output += "</select></form>";
|
229
241
|
this.element.html(output);
|
230
242
|
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
243
|
+
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
244
|
+
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
245
|
+
this.element.find("select")[0].focus();
|
231
246
|
},
|
232
247
|
|
233
248
|
getValue : function() {
|
@@ -236,6 +251,10 @@ BestInPlaceEditor.forms = {
|
|
236
251
|
|
237
252
|
blurHandler : function(event) {
|
238
253
|
event.data.editor.update();
|
254
|
+
},
|
255
|
+
|
256
|
+
keyupHandler : function(event) {
|
257
|
+
if (event.keyCode == 27) event.data.editor.abort();
|
239
258
|
}
|
240
259
|
},
|
241
260
|
|
@@ -254,11 +273,21 @@ BestInPlaceEditor.forms = {
|
|
254
273
|
|
255
274
|
"textarea" : {
|
256
275
|
activateForm : function() {
|
276
|
+
// grab width and height of text
|
277
|
+
width = this.element.css('width');
|
278
|
+
height = this.element.css('height');
|
279
|
+
|
280
|
+
// construct the form
|
257
281
|
var output = '<form action="javascript:void(0)" style="display:inline;"><textarea>';
|
258
282
|
output += this.sanitizeValue(this.oldValue);
|
259
283
|
output += '</textarea></form>';
|
260
284
|
this.element.html(output);
|
261
|
-
|
285
|
+
|
286
|
+
// set width and height of textarea
|
287
|
+
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
288
|
+
jQuery(this.element.find("textarea")[0]).elastic();
|
289
|
+
|
290
|
+
this.element.find("textarea")[0].focus();
|
262
291
|
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
263
292
|
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
264
293
|
},
|
@@ -273,7 +302,13 @@ BestInPlaceEditor.forms = {
|
|
273
302
|
|
274
303
|
keyupHandler : function(event) {
|
275
304
|
if (event.keyCode == 27) {
|
276
|
-
event.data.editor
|
305
|
+
BestInPlaceEditor.forms.textarea.abort(event.data.editor);
|
306
|
+
}
|
307
|
+
},
|
308
|
+
|
309
|
+
abort : function(editor) {
|
310
|
+
if (confirm("Are you sure you want to discard your changes?")) {
|
311
|
+
editor.abort();
|
277
312
|
}
|
278
313
|
}
|
279
314
|
}
|
@@ -284,4 +319,138 @@ jQuery.fn.best_in_place = function() {
|
|
284
319
|
jQuery(this).data('bestInPlaceEditor', new BestInPlaceEditor(this));
|
285
320
|
});
|
286
321
|
return this;
|
287
|
-
};
|
322
|
+
};
|
323
|
+
|
324
|
+
|
325
|
+
|
326
|
+
/**
|
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+
|
331
|
+
*
|
332
|
+
* @author Jan Jarfalk
|
333
|
+
* @author-email jan.jarfalk@unwrongest.com
|
334
|
+
* @author-website http://www.unwrongest.com
|
335
|
+
*
|
336
|
+
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
337
|
+
*/
|
338
|
+
|
339
|
+
(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,'&').replace(/ /g, ' ').replace(/<|>/g, '>').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+' ' != 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+' ');
|
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); });
|
448
|
+
|
449
|
+
// Run update once when elastic is initialized
|
450
|
+
update();
|
451
|
+
|
452
|
+
});
|
453
|
+
|
454
|
+
}
|
455
|
+
});
|
456
|
+
})(jQuery);
|
@@ -24,16 +24,13 @@ input[type=checkbox] {
|
|
24
24
|
width: 1em;
|
25
25
|
}
|
26
26
|
textarea {
|
27
|
-
height:15em;
|
28
|
-
width:
|
27
|
+
max-height:15em;
|
28
|
+
min-width: 40em;
|
29
29
|
}
|
30
30
|
.best_in_place {
|
31
31
|
padding: .1m;
|
32
32
|
cursor: hand;
|
33
33
|
cursor: pointer;
|
34
|
-
-moz-transition: background 0.5s linear;
|
35
|
-
-o-transition: background 0.5s linear;
|
36
|
-
-webkit-transition: background 0.5s linear;
|
37
34
|
-moz-border-radius: 5px;
|
38
35
|
-webkit-border-radius: 5px;
|
39
36
|
-o-border-radius: 5px;
|
@@ -62,7 +59,13 @@ textarea {
|
|
62
59
|
padding: 20px;
|
63
60
|
background-color: #000000;
|
64
61
|
color: #FFFFFF;
|
65
|
-
border: 2px solid #
|
62
|
+
border: 2px solid #AAA;
|
63
|
+
-moz-border-radius: 10px;
|
64
|
+
-webkit-border-radius: 10px;
|
65
|
+
-o-border-radius: 10px;
|
66
|
+
-ms-border-radius: 10px;
|
67
|
+
-khtml-border-radius: 10px;
|
68
|
+
border-radius: 10px;
|
66
69
|
}
|
67
70
|
.purr:hover .close {
|
68
71
|
position: absolute;
|
@@ -1,17 +1,17 @@
|
|
1
1
|
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
2
2
|
|
3
3
|
one:
|
4
|
-
name:
|
5
|
-
last_name:
|
6
|
-
address:
|
7
|
-
email:
|
8
|
-
zip:
|
9
|
-
country:
|
4
|
+
name: testy
|
5
|
+
last_name: tester
|
6
|
+
address: 123 main st
|
7
|
+
email: testytester@example.com
|
8
|
+
zip: 12345
|
9
|
+
country: 1
|
10
10
|
|
11
11
|
two:
|
12
|
-
name:
|
13
|
-
last_name:
|
14
|
-
address:
|
15
|
-
email:
|
16
|
-
zip:
|
17
|
-
country:
|
12
|
+
name: mystery
|
13
|
+
last_name: man
|
14
|
+
address: PO Box 0, Nowhere
|
15
|
+
email: misterioso@example.com
|
16
|
+
zip: 99999
|
17
|
+
country:
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: best_in_place
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 9
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 9
|
10
|
+
version: 0.1.9
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Bernat Farrero
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-12 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|