best_in_place 0.1.8 → 0.1.9
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 +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
|