best_in_place 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -0
- data/best_in_place.gemspec +1 -1
- data/lib/assets/javascripts/best_in_place.js +115 -24
- data/lib/best_in_place.rb +3 -4
- data/lib/best_in_place/check_version.rb +8 -0
- data/lib/best_in_place/helper.rb +3 -1
- data/lib/best_in_place/railtie.rb +7 -0
- data/lib/best_in_place/version.rb +1 -1
- data/spec/helpers/best_in_place_spec.rb +31 -1
- data/spec/integration/js_spec.rb +197 -2
- data/spec/integration/text_area_spec.rb +9 -0
- data/test_app/app/assets/stylesheets/style.css.erb +1 -1
- data/test_app/app/models/user.rb +1 -1
- data/test_app/app/views/users/show.html.erb +22 -0
- data/test_app/db/migrate/20111210084202_add_favorite_color_to_users.rb +5 -0
- data/test_app/db/migrate/20111210084251_add_favorite_books_to_users.rb +5 -0
- data/test_app/db/schema.rb +3 -3
- metadata +19 -15
data/README.md
CHANGED
@@ -25,6 +25,7 @@ The editor works by PUTting the updated value to the server and GETting the upda
|
|
25
25
|
- Sanitize HTML and trim spaces of user's input on user's choice
|
26
26
|
- Displays server-side **validation** errors
|
27
27
|
- Allows external activator
|
28
|
+
- Allows optional, configurable OK and Cancel buttons for inputs and textareas
|
28
29
|
- ESC key destroys changes (requires user confirmation)
|
29
30
|
- Autogrowing textarea
|
30
31
|
- Helper for generating the best_in_place field only if a condition is satisfied
|
@@ -51,11 +52,14 @@ Options:
|
|
51
52
|
- **:nil**: The nil param defines the content displayed in case no value is defined for that field. It can be something like "click me to edit".
|
52
53
|
If not defined it will show *"-"*.
|
53
54
|
- **:activator**: Is the DOM object that can activate the field. If not defined the user will making editable by clicking on it.
|
55
|
+
- **:ok_button**: (Inputs and textareas only) If set to a string, then an OK button will be shown with the string as its label, replacing save on blur.
|
56
|
+
- **:cancel_button**: (Inputs and textareas only) If set to a string, then a Cancel button will be shown with the string as its label.
|
54
57
|
- **:sanitize**: True by default. If set to false the input/textarea will accept html tags.
|
55
58
|
- **:html_attrs**: Hash of html arguments, such as maxlength, default-value etc.
|
56
59
|
- **:inner_class**: Class that is set to the rendered form.
|
57
60
|
- **:display_as**: A model method which will be called in order to display
|
58
61
|
this field.
|
62
|
+
- **:object_name**: Used for overriding the default params key used for the object (the data-object attribute). Useful for e.g. STI scenarios where best_in_place should post to a common controller for different models.
|
59
63
|
|
60
64
|
###best_in_place_if
|
61
65
|
**best_in_place_if condition, object, field, OPTIONS**
|
@@ -97,6 +101,8 @@ Examples (code in the views):
|
|
97
101
|
|
98
102
|
<%= best_in_place @user, :description, :type => :textarea %>
|
99
103
|
|
104
|
+
<%= best_in_place @user, :favorite_books, :type => :textarea, :ok_button => 'Save', :cancel_button => 'Cancel' %>
|
105
|
+
|
100
106
|
### Select
|
101
107
|
|
102
108
|
<%= best_in_place @user, :country, :type => :select, :collection => [[1, "Spain"], [2, "Italy"], [3, "Germany"], [4, "France"]] %>
|
data/best_in_place.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
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.1
|
22
|
+
s.add_dependency "rails", "~> 3.1"
|
23
23
|
s.add_dependency "jquery-rails"
|
24
24
|
|
25
25
|
s.add_development_dependency "rspec-rails", "~> 2.7.0"
|
@@ -55,6 +55,12 @@ BestInPlaceEditor.prototype = {
|
|
55
55
|
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
56
56
|
},
|
57
57
|
|
58
|
+
abortIfConfirm : function () {
|
59
|
+
if (confirm("Are you sure you want to discard your changes?")) {
|
60
|
+
this.abort();
|
61
|
+
}
|
62
|
+
},
|
63
|
+
|
58
64
|
update : function() {
|
59
65
|
var editor = this;
|
60
66
|
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
@@ -101,6 +107,9 @@ BestInPlaceEditor.prototype = {
|
|
101
107
|
self.formType = self.formType || jQuery(this).attr("data-type");
|
102
108
|
self.objectName = self.objectName || jQuery(this).attr("data-object");
|
103
109
|
self.attributeName = self.attributeName || jQuery(this).attr("data-attribute");
|
110
|
+
self.activator = self.activator || jQuery(this).attr("data-activator");
|
111
|
+
self.okButton = self.okButton || jQuery(this).attr("data-ok-button");
|
112
|
+
self.cancelButton = self.cancelButton || jQuery(this).attr("data-cancel-button");
|
104
113
|
self.nil = self.nil || jQuery(this).attr("data-nil");
|
105
114
|
self.inner_class = self.inner_class || jQuery(this).attr("data-inner-class");
|
106
115
|
self.html_attrs = self.html_attrs || jQuery(this).attr("data-html-attrs");
|
@@ -116,15 +125,17 @@ BestInPlaceEditor.prototype = {
|
|
116
125
|
});
|
117
126
|
|
118
127
|
// Load own attributes (overrides all others)
|
119
|
-
self.url = self.element.attr("data-url")
|
120
|
-
self.collection = self.element.attr("data-collection")
|
121
|
-
self.formType = self.element.attr("data-type")
|
122
|
-
self.objectName = self.element.attr("data-object")
|
123
|
-
self.attributeName = self.element.attr("data-attribute")
|
124
|
-
self.activator = self.element.attr("data-activator")
|
125
|
-
self.
|
126
|
-
self.
|
127
|
-
self.
|
128
|
+
self.url = self.element.attr("data-url") || self.url || document.location.pathname;
|
129
|
+
self.collection = self.element.attr("data-collection") || self.collection;
|
130
|
+
self.formType = self.element.attr("data-type") || self.formtype || "input";
|
131
|
+
self.objectName = self.element.attr("data-object") || self.objectName;
|
132
|
+
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
133
|
+
self.activator = self.element.attr("data-activator") || self.element;
|
134
|
+
self.okButton = self.element.attr("data-ok-button") || self.okButton;
|
135
|
+
self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
|
136
|
+
self.nil = self.element.attr("data-nil") || self.nil || "-";
|
137
|
+
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
138
|
+
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
128
139
|
self.original_content = self.element.attr("data-original-content") || self.original_content;
|
129
140
|
|
130
141
|
if (!self.element.attr("data-sanitize")) {
|
@@ -165,7 +176,7 @@ BestInPlaceEditor.prototype = {
|
|
165
176
|
tmp.innerHTML = s;
|
166
177
|
s = tmp.textContent || tmp.innerText;
|
167
178
|
}
|
168
|
-
return jQuery.trim(s);
|
179
|
+
return jQuery.trim(s).replace(/"/g, '"');
|
169
180
|
},
|
170
181
|
|
171
182
|
/* Generate the data sent in the POST request */
|
@@ -232,6 +243,11 @@ BestInPlaceEditor.prototype = {
|
|
232
243
|
};
|
233
244
|
|
234
245
|
|
246
|
+
// Button cases:
|
247
|
+
// If no buttons, then blur saves, ESC cancels
|
248
|
+
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
249
|
+
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
250
|
+
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
235
251
|
BestInPlaceEditor.forms = {
|
236
252
|
"input" : {
|
237
253
|
activateForm : function() {
|
@@ -240,27 +256,65 @@ BestInPlaceEditor.forms = {
|
|
240
256
|
if (this.inner_class != null) {
|
241
257
|
output += ' class="' + this.inner_class + '"';
|
242
258
|
}
|
243
|
-
output += '
|
259
|
+
output += '>';
|
260
|
+
if (this.okButton) {
|
261
|
+
output += '<input type="submit" value="' + this.okButton + '" />'
|
262
|
+
}
|
263
|
+
if (this.cancelButton) {
|
264
|
+
output += '<input type="button" value="' + this.cancelButton + '" />'
|
265
|
+
}
|
266
|
+
output += '</form>';
|
244
267
|
this.element.html(output);
|
245
268
|
this.setHtmlAttributes();
|
246
|
-
this.element.find(
|
269
|
+
this.element.find("input[type='text']")[0].select();
|
247
270
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
248
|
-
this.
|
249
|
-
|
271
|
+
if (this.cancelButton) {
|
272
|
+
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler)
|
273
|
+
}
|
274
|
+
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
275
|
+
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
276
|
+
this.blurTimer = null;
|
277
|
+
this.userClicked = false;
|
250
278
|
},
|
251
279
|
|
252
|
-
getValue :
|
280
|
+
getValue : function() {
|
253
281
|
return this.sanitizeValue(this.element.find("input").val());
|
254
282
|
},
|
255
283
|
|
284
|
+
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
256
285
|
inputBlurHandler : function(event) {
|
257
|
-
event.data.editor.
|
286
|
+
if (event.data.editor.okButton) {
|
287
|
+
event.data.editor.blurTimer = setTimeout(function () {
|
288
|
+
if (!event.data.editor.userClicked) {
|
289
|
+
event.data.editor.abort();
|
290
|
+
}
|
291
|
+
}, 500);
|
292
|
+
} else {
|
293
|
+
if (event.data.editor.cancelButton) {
|
294
|
+
event.data.editor.blurTimer = setTimeout(function () {
|
295
|
+
if (!event.data.editor.userClicked) {
|
296
|
+
event.data.editor.update();
|
297
|
+
}
|
298
|
+
}, 500);
|
299
|
+
} else {
|
300
|
+
event.data.editor.update();
|
301
|
+
}
|
302
|
+
}
|
258
303
|
},
|
259
304
|
|
260
305
|
submitHandler : function(event) {
|
306
|
+
event.data.editor.userClicked = true;
|
307
|
+
clearTimeout(event.data.editor.blurTimer);
|
261
308
|
event.data.editor.update();
|
262
309
|
},
|
263
310
|
|
311
|
+
cancelButtonHandler : function(event) {
|
312
|
+
event.data.editor.userClicked = true;
|
313
|
+
clearTimeout(event.data.editor.blurTimer);
|
314
|
+
event.data.editor.abort();
|
315
|
+
event.stopPropagation(); // Without this, click isn't handled
|
316
|
+
},
|
317
|
+
|
264
318
|
keyupHandler : function(event) {
|
265
319
|
if (event.keyCode == 27) {
|
266
320
|
event.data.editor.abort();
|
@@ -361,7 +415,14 @@ BestInPlaceEditor.forms = {
|
|
361
415
|
// construct the form
|
362
416
|
var output = '<form action="javascript:void(0)" style="display:inline;"><textarea>';
|
363
417
|
output += this.sanitizeValue(this.display_value);
|
364
|
-
output += '</textarea
|
418
|
+
output += '</textarea>';
|
419
|
+
if (this.okButton) {
|
420
|
+
output += '<input type="submit" value="' + this.okButton + '" />'
|
421
|
+
}
|
422
|
+
if (this.cancelButton) {
|
423
|
+
output += '<input type="button" value="' + this.cancelButton + '" />'
|
424
|
+
}
|
425
|
+
output += '</form>';
|
365
426
|
this.element.html(output);
|
366
427
|
this.setHtmlAttributes();
|
367
428
|
|
@@ -370,27 +431,57 @@ BestInPlaceEditor.forms = {
|
|
370
431
|
jQuery(this.element.find("textarea")[0]).elastic();
|
371
432
|
|
372
433
|
this.element.find("textarea")[0].focus();
|
434
|
+
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
435
|
+
if (this.cancelButton) {
|
436
|
+
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler)
|
437
|
+
}
|
373
438
|
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
374
439
|
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
440
|
+
this.blurTimer = null;
|
441
|
+
this.userClicked = false;
|
375
442
|
},
|
376
443
|
|
377
444
|
getValue : function() {
|
378
445
|
return this.sanitizeValue(this.element.find("textarea").val());
|
379
446
|
},
|
380
447
|
|
448
|
+
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
381
449
|
blurHandler : function(event) {
|
450
|
+
if (event.data.editor.okButton) {
|
451
|
+
event.data.editor.blurTimer = setTimeout(function () {
|
452
|
+
if (!event.data.editor.userClicked) {
|
453
|
+
event.data.editor.abortIfConfirm();
|
454
|
+
}
|
455
|
+
}, 500);
|
456
|
+
} else {
|
457
|
+
if (event.data.editor.cancelButton) {
|
458
|
+
event.data.editor.blurTimer = setTimeout(function () {
|
459
|
+
if (!event.data.editor.userClicked) {
|
460
|
+
event.data.editor.update();
|
461
|
+
}
|
462
|
+
}, 500);
|
463
|
+
} else {
|
464
|
+
event.data.editor.update();
|
465
|
+
}
|
466
|
+
}
|
467
|
+
},
|
468
|
+
|
469
|
+
submitHandler : function(event) {
|
470
|
+
event.data.editor.userClicked = true;
|
471
|
+
clearTimeout(event.data.editor.blurTimer);
|
382
472
|
event.data.editor.update();
|
383
473
|
},
|
384
474
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
475
|
+
cancelButtonHandler : function(event) {
|
476
|
+
event.data.editor.userClicked = true;
|
477
|
+
clearTimeout(event.data.editor.blurTimer);
|
478
|
+
event.data.editor.abortIfConfirm();
|
479
|
+
event.stopPropagation(); // Without this, click isn't handled
|
389
480
|
},
|
390
481
|
|
391
|
-
|
392
|
-
if (
|
393
|
-
editor.
|
482
|
+
keyupHandler : function(event) {
|
483
|
+
if (event.keyCode == 27) {
|
484
|
+
event.data.editor.abortIfConfirm();
|
394
485
|
}
|
395
486
|
}
|
396
487
|
}
|
data/lib/best_in_place.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
+
require "best_in_place/check_version"
|
1
2
|
require "best_in_place/utils"
|
2
3
|
require "best_in_place/helper"
|
3
4
|
require "best_in_place/engine"
|
5
|
+
require "best_in_place/railtie"
|
4
6
|
require "best_in_place/controller_extensions"
|
5
7
|
require "best_in_place/display_methods"
|
8
|
+
require "action_view"
|
6
9
|
|
7
10
|
module BestInPlace
|
8
11
|
autoload :TestHelpers, "best_in_place/test_helpers"
|
9
|
-
|
10
|
-
module ViewHelpers
|
11
|
-
extend ActionView::Helpers
|
12
|
-
end
|
13
12
|
end
|
data/lib/best_in_place/helper.rb
CHANGED
@@ -33,10 +33,12 @@ module BestInPlace
|
|
33
33
|
out = "<span class='best_in_place'"
|
34
34
|
out << " id='#{BestInPlace::Utils.build_best_in_place_id(object, field)}'"
|
35
35
|
out << " data-url='#{opts[:path].blank? ? url_for(object) : url_for(opts[:path])}'"
|
36
|
-
out << " data-object='#{object.class.to_s.gsub("::", "_").underscore}'"
|
36
|
+
out << " data-object='#{opts[:object_name] || object.class.to_s.gsub("::", "_").underscore}'"
|
37
37
|
out << " data-collection='#{collection.gsub(/'/, "'")}'" unless collection.blank?
|
38
38
|
out << " data-attribute='#{field}'"
|
39
39
|
out << " data-activator='#{opts[:activator]}'" unless opts[:activator].blank?
|
40
|
+
out << " data-ok-button='#{opts[:ok_button]}'" unless opts[:ok_button].blank?
|
41
|
+
out << " data-cancel-button='#{opts[:cancel_button]}'" unless opts[:cancel_button].blank?
|
40
42
|
out << " data-nil='#{opts[:nil]}'" unless opts[:nil].blank?
|
41
43
|
out << " data-type='#{opts[:type]}'"
|
42
44
|
out << " data-inner-class='#{opts[:inner_class]}'" if opts[:inner_class]
|
@@ -62,6 +62,14 @@ describe BestInPlace::BestInPlaceHelpers do
|
|
62
62
|
@span.attribute("data-activator").should be_nil
|
63
63
|
end
|
64
64
|
|
65
|
+
it "should have no OK button text by default" do
|
66
|
+
@span.attribute("data-ok-button").should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have no Cancel button text by default" do
|
70
|
+
@span.attribute("data-cancel-button").should be_nil
|
71
|
+
end
|
72
|
+
|
65
73
|
it "should have no inner_class by default" do
|
66
74
|
@span.attribute("data-inner-class").should be_nil
|
67
75
|
end
|
@@ -130,6 +138,29 @@ describe BestInPlace::BestInPlaceHelpers do
|
|
130
138
|
span.attribute("data-activator").value.should == "awesome"
|
131
139
|
end
|
132
140
|
|
141
|
+
it "should have the given OK button text" do
|
142
|
+
out = helper.best_in_place @user, :name, :ok_button => "okay"
|
143
|
+
nk = Nokogiri::HTML.parse(out)
|
144
|
+
span = nk.css("span")
|
145
|
+
span.attribute("data-ok-button").value.should == "okay"
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should have the given Cancel button text" do
|
149
|
+
out = helper.best_in_place @user, :name, :cancel_button => "nasty"
|
150
|
+
nk = Nokogiri::HTML.parse(out)
|
151
|
+
span = nk.css("span")
|
152
|
+
span.attribute("data-cancel-button").value.should == "nasty"
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "object_name" do
|
156
|
+
it "should change the data-object value" do
|
157
|
+
out = helper.best_in_place @user, :name, :object_name => "my_user"
|
158
|
+
nk = Nokogiri::HTML.parse(out)
|
159
|
+
span = nk.css("span")
|
160
|
+
span.attribute("data-object").value.should == "my_user"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
133
164
|
describe "display_as" do
|
134
165
|
it "should render the address with a custom renderer" do
|
135
166
|
@user.should_receive(:address_format).and_return("the result")
|
@@ -161,7 +192,6 @@ describe BestInPlace::BestInPlaceHelpers do
|
|
161
192
|
end
|
162
193
|
end
|
163
194
|
|
164
|
-
|
165
195
|
context "with a text field attribute" do
|
166
196
|
before do
|
167
197
|
nk = Nokogiri::HTML.parse(helper.best_in_place @user, :name)
|
data/spec/integration/js_spec.rb
CHANGED
@@ -12,7 +12,10 @@ describe "JS behaviour", :js => true do
|
|
12
12
|
:receive_email => false,
|
13
13
|
:birth_date => Time.now.utc,
|
14
14
|
:description => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus a lectus et lacus ultrices auctor. Morbi aliquet convallis tincidunt. Praesent enim libero, iaculis at commodo nec, fermentum a dolor. Quisque eget eros id felis lacinia faucibus feugiat et ante. Aenean justo nisi, aliquam vel egestas vel, porta in ligula. Etiam molestie, lacus eget tincidunt accumsan, elit justo rhoncus urna, nec pretium neque mi et lorem. Aliquam posuere, dolor quis pulvinar luctus, felis dolor tincidunt leo, eget pretium orci purus ac nibh. Ut enim sem, suscipit ac elementum vitae, sodales vel sem.",
|
15
|
-
:money => 100
|
15
|
+
:money => 100,
|
16
|
+
:favorite_color => 'Red',
|
17
|
+
:favorite_books => "The City of Gold and Lead",
|
18
|
+
:description => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus a lectus et lacus ultrices auctor. Morbi aliquet convallis tincidunt. Praesent enim libero, iaculis at commodo nec, fermentum a dolor. Quisque eget eros id felis lacinia faucibus feugiat et ante. Aenean justo nisi, aliquam vel egestas vel, porta in ligula. Etiam molestie, lacus eget tincidunt accumsan, elit justo rhoncus urna, nec pretium neque mi et lorem. Aliquam posuere, dolor quis pulvinar luctus, felis dolor tincidunt leo, eget pretium orci purus ac nibh. Ut enim sem, suscipit ac elementum vitae, sodales vel sem."
|
16
19
|
end
|
17
20
|
|
18
21
|
describe "nil option" do
|
@@ -178,6 +181,182 @@ describe "JS behaviour", :js => true do
|
|
178
181
|
end
|
179
182
|
end
|
180
183
|
|
184
|
+
it "should correctly use an OK submit button when so configured for an input" do
|
185
|
+
@user.save!
|
186
|
+
visit user_path(@user)
|
187
|
+
|
188
|
+
within("#favorite_color") do
|
189
|
+
page.should have_content('Red')
|
190
|
+
end
|
191
|
+
|
192
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_color
|
193
|
+
page.execute_script <<-JS
|
194
|
+
$("##{id}").click();
|
195
|
+
$("##{id} input[name='favorite_color']").val('Blue');
|
196
|
+
$("##{id} input[type='submit']").click();
|
197
|
+
JS
|
198
|
+
|
199
|
+
visit user_path(@user)
|
200
|
+
within("#favorite_color") do
|
201
|
+
page.should have_content('Blue')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should correctly use a Cancel button when so configured for an input" do
|
206
|
+
@user.save!
|
207
|
+
visit user_path(@user)
|
208
|
+
|
209
|
+
within("#favorite_color") do
|
210
|
+
page.should have_content('Red')
|
211
|
+
end
|
212
|
+
|
213
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_color
|
214
|
+
page.execute_script <<-JS
|
215
|
+
$("##{id}").click();
|
216
|
+
$("##{id} input[name='favorite_color']").val('Blue');
|
217
|
+
$("##{id} input[type='button']").click();
|
218
|
+
JS
|
219
|
+
|
220
|
+
visit user_path(@user)
|
221
|
+
within("#favorite_color") do
|
222
|
+
page.should have_content('Red')
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should not submit input on blur if there's an OK button present" do
|
227
|
+
@user.save!
|
228
|
+
visit user_path(@user)
|
229
|
+
|
230
|
+
within("#favorite_color") do
|
231
|
+
page.should have_content('Red')
|
232
|
+
end
|
233
|
+
|
234
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_color
|
235
|
+
page.execute_script <<-JS
|
236
|
+
$("##{id}").click();
|
237
|
+
$("##{id} input[name='favorite_color']").val('Blue');
|
238
|
+
$("##{id} input[name='favorite_color']").blur();
|
239
|
+
JS
|
240
|
+
sleep 1 # Increase if browser is slow
|
241
|
+
|
242
|
+
visit user_path(@user)
|
243
|
+
within("#favorite_color") do
|
244
|
+
page.should have_content('Red')
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should still submit input on blur if there's only a Cancel button present" do
|
249
|
+
@user.save!
|
250
|
+
visit user_path(@user, :suppress_ok_button => 1)
|
251
|
+
|
252
|
+
within("#favorite_color") do
|
253
|
+
page.should have_content('Red')
|
254
|
+
end
|
255
|
+
|
256
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_color
|
257
|
+
page.execute_script %{$("##{id}").click();}
|
258
|
+
page.should have_no_css("##{id} input[type='submit']")
|
259
|
+
page.execute_script <<-JS
|
260
|
+
$("##{id} input[name='favorite_color']").val('Blue');
|
261
|
+
$("##{id} input[name='favorite_color']").blur();
|
262
|
+
JS
|
263
|
+
sleep 1 # Increase if browser is slow
|
264
|
+
|
265
|
+
visit user_path(@user)
|
266
|
+
within("#favorite_color") do
|
267
|
+
page.should have_content('Blue')
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should correctly use an OK submit button when so configured for a text area" do
|
272
|
+
@user.save!
|
273
|
+
visit user_path(@user)
|
274
|
+
|
275
|
+
within("#favorite_books") do
|
276
|
+
page.should have_content('The City of Gold and Lead')
|
277
|
+
end
|
278
|
+
|
279
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_books
|
280
|
+
page.execute_script <<-JS
|
281
|
+
$("##{id}").click();
|
282
|
+
$("##{id} textarea").val('1Q84');
|
283
|
+
$("##{id} input[type='submit']").click();
|
284
|
+
JS
|
285
|
+
|
286
|
+
visit user_path(@user)
|
287
|
+
within("#favorite_books") do
|
288
|
+
page.should have_content('1Q84')
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should correctly use a Cancel button when so configured for a text area" do
|
293
|
+
@user.save!
|
294
|
+
visit user_path(@user)
|
295
|
+
|
296
|
+
within("#favorite_books") do
|
297
|
+
page.should have_content('The City of Gold and Lead')
|
298
|
+
end
|
299
|
+
|
300
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_books
|
301
|
+
page.execute_script <<-JS
|
302
|
+
$("##{id}").click();
|
303
|
+
$("##{id} textarea").val('1Q84');
|
304
|
+
$("##{id} input[type='button']").click();
|
305
|
+
JS
|
306
|
+
page.driver.browser.switch_to.alert.accept
|
307
|
+
|
308
|
+
visit user_path(@user)
|
309
|
+
within("#favorite_books") do
|
310
|
+
page.should have_content('The City of Gold and Lead')
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should not submit text area on blur if there's an OK button present" do
|
315
|
+
@user.save!
|
316
|
+
visit user_path(@user)
|
317
|
+
|
318
|
+
within("#favorite_books") do
|
319
|
+
page.should have_content('The City of Gold and Lead')
|
320
|
+
end
|
321
|
+
|
322
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_books
|
323
|
+
page.execute_script <<-JS
|
324
|
+
$("##{id}").click();
|
325
|
+
$("##{id} textarea").val('1Q84');
|
326
|
+
$("##{id} textarea").blur();
|
327
|
+
JS
|
328
|
+
sleep 1 # Increase if browser is slow
|
329
|
+
page.driver.browser.switch_to.alert.accept
|
330
|
+
|
331
|
+
visit user_path(@user)
|
332
|
+
within("#favorite_books") do
|
333
|
+
page.should have_content('The City of Gold and Lead')
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
it "should still submit text area on blur if there's only a Cancel button present" do
|
338
|
+
@user.save!
|
339
|
+
visit user_path(@user, :suppress_ok_button => 1)
|
340
|
+
|
341
|
+
within("#favorite_books") do
|
342
|
+
page.should have_content('The City of Gold and Lead')
|
343
|
+
end
|
344
|
+
|
345
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :favorite_books
|
346
|
+
page.execute_script %{$("##{id}").click();}
|
347
|
+
page.should have_no_css("##{id} input[type='submit']")
|
348
|
+
page.execute_script <<-JS
|
349
|
+
$("##{id} textarea").val('1Q84');
|
350
|
+
$("##{id} textarea").blur();
|
351
|
+
JS
|
352
|
+
sleep 1 # Increase if browser is slow
|
353
|
+
|
354
|
+
visit user_path(@user)
|
355
|
+
within("#favorite_books") do
|
356
|
+
page.should have_content('1Q84')
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
181
360
|
it "should show validation errors" do
|
182
361
|
@user.save!
|
183
362
|
visit user_path(@user)
|
@@ -377,5 +556,21 @@ describe "JS behaviour", :js => true do
|
|
377
556
|
end
|
378
557
|
|
379
558
|
end
|
380
|
-
end
|
381
559
|
|
560
|
+
it "should display strings with quotes correctly in fields" do
|
561
|
+
@user.last_name = "A last name \"with double quotes\""
|
562
|
+
@user.save!
|
563
|
+
|
564
|
+
retry_on_timeout do
|
565
|
+
visit user_path(@user)
|
566
|
+
|
567
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :last_name
|
568
|
+
page.execute_script <<-JS
|
569
|
+
$("##{id}").click();
|
570
|
+
JS
|
571
|
+
|
572
|
+
text = page.find("##{id} input").value
|
573
|
+
text.should == "A last name \"with double quotes\""
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
@@ -27,4 +27,13 @@ describe "JS behaviour", :js => true do
|
|
27
27
|
page.should have_content("A new description")
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
it "should be able to use a bip_text with :display_with option" do
|
32
|
+
@user.description = "I'm so awesome"
|
33
|
+
@user.save!
|
34
|
+
visit user_path(@user)
|
35
|
+
within("#dw_description") do
|
36
|
+
page.should have_content("I'm so awesome")
|
37
|
+
end
|
38
|
+
end
|
30
39
|
end
|
data/test_app/app/models/user.rb
CHANGED
@@ -3,7 +3,7 @@ class User < ActiveRecord::Base
|
|
3
3
|
:length => { :minimum => 2, :maximum => 24, :message => "has invalid length"},
|
4
4
|
:presence => {:message => "can't be blank"}
|
5
5
|
validates :last_name,
|
6
|
-
:length => { :minimum => 2, :maximum =>
|
6
|
+
:length => { :minimum => 2, :maximum => 50, :message => "has invalid length"},
|
7
7
|
:presence => {:message => "can't be blank"}
|
8
8
|
validates :address,
|
9
9
|
:length => { :minimum => 5, :message => "too short length"},
|
@@ -52,12 +52,34 @@
|
|
52
52
|
<%= best_in_place @user, :receive_email, :type => :checkbox, :collection => ["No thanks", "Yes of course"] %>
|
53
53
|
</td>
|
54
54
|
</tr>
|
55
|
+
<tr>
|
56
|
+
<td>Favorite color</td>
|
57
|
+
<td id="favorite_color">
|
58
|
+
<%- opts = { :ok_button => 'Do it!', :cancel_button => 'Nope' } %>
|
59
|
+
<%- opts.delete(:ok_button) if params[:suppress_ok_button] %>
|
60
|
+
<%= best_in_place @user, :favorite_color, opts %>
|
61
|
+
</td>
|
62
|
+
</tr>
|
63
|
+
<tr>
|
64
|
+
<td>Favorite books</td>
|
65
|
+
<td id="favorite_books">
|
66
|
+
<%- opts = { :type => :textarea, :ok_button => 'Save', :cancel_button => 'Cancel' } %>
|
67
|
+
<%- opts.delete(:ok_button) if params[:suppress_ok_button] %>
|
68
|
+
<%= best_in_place @user, :favorite_books, opts %>
|
69
|
+
</td>
|
70
|
+
</tr>
|
55
71
|
<tr>
|
56
72
|
<td>User description</td>
|
57
73
|
<td id="description">
|
58
74
|
<%= best_in_place @user, :description, :display_as => :markdown_desc, :type => :textarea, :sanitize => false %>
|
59
75
|
</td>
|
60
76
|
</tr>
|
77
|
+
<tr>
|
78
|
+
<td>Simple-formatted user description</td>
|
79
|
+
<td id="dw_description">
|
80
|
+
<%= best_in_place @user, :description, :display_with => :simple_format, :type => :textarea %>
|
81
|
+
</td>
|
82
|
+
</tr>
|
61
83
|
<tr>
|
62
84
|
<td>Money</td>
|
63
85
|
<td id="money">
|
data/test_app/db/schema.rb
CHANGED
@@ -12,12 +12,11 @@
|
|
12
12
|
# It's strongly recommended to check this file into your version control system.
|
13
13
|
|
14
14
|
ActiveRecord::Schema.define(:version => 20111224181356) do
|
15
|
-
|
16
15
|
create_table "users", :force => true do |t|
|
17
16
|
t.string "name"
|
18
17
|
t.string "last_name"
|
19
18
|
t.string "address"
|
20
|
-
t.string "email",
|
19
|
+
t.string "email", :null => false
|
21
20
|
t.string "zip"
|
22
21
|
t.string "country"
|
23
22
|
t.datetime "created_at"
|
@@ -26,6 +25,7 @@ ActiveRecord::Schema.define(:version => 20111224181356) do
|
|
26
25
|
t.text "description"
|
27
26
|
t.datetime "birth_date"
|
28
27
|
t.float "money"
|
28
|
+
t.string "favorite_color"
|
29
|
+
t.text "favorite_books"
|
29
30
|
end
|
30
|
-
|
31
31
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: best_in_place
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &83572300 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 3.1
|
21
|
+
version: '3.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *83572300
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: jquery-rails
|
27
|
-
requirement: &
|
27
|
+
requirement: &83572040 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *83572040
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec-rails
|
38
|
-
requirement: &
|
38
|
+
requirement: &83571670 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 2.7.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *83571670
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: nokogiri
|
49
|
-
requirement: &
|
49
|
+
requirement: &83571350 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.5.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *83571350
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: capybara
|
60
|
-
requirement: &
|
60
|
+
requirement: &83571040 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: 1.0.1
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *83571040
|
69
69
|
description: BestInPlace is a jQuery script and a Rails 3 helper that provide the
|
70
70
|
method best_in_place to display any object field easily editable for the user by
|
71
71
|
just clicking on it. It supports input data, text data, boolean data and custom
|
@@ -86,10 +86,12 @@ files:
|
|
86
86
|
- lib/assets/javascripts/best_in_place.js
|
87
87
|
- lib/assets/javascripts/jquery.purr.js
|
88
88
|
- lib/best_in_place.rb
|
89
|
+
- lib/best_in_place/check_version.rb
|
89
90
|
- lib/best_in_place/controller_extensions.rb
|
90
91
|
- lib/best_in_place/display_methods.rb
|
91
92
|
- lib/best_in_place/engine.rb
|
92
93
|
- lib/best_in_place/helper.rb
|
94
|
+
- lib/best_in_place/railtie.rb
|
93
95
|
- lib/best_in_place/test_helpers.rb
|
94
96
|
- lib/best_in_place/utils.rb
|
95
97
|
- lib/best_in_place/version.rb
|
@@ -154,6 +156,8 @@ files:
|
|
154
156
|
- test_app/db/migrate/20101206205922_create_users.rb
|
155
157
|
- test_app/db/migrate/20101212170114_add_receive_email_to_user.rb
|
156
158
|
- test_app/db/migrate/20110115204441_add_description_to_user.rb
|
159
|
+
- test_app/db/migrate/20111210084202_add_favorite_color_to_users.rb
|
160
|
+
- test_app/db/migrate/20111210084251_add_favorite_books_to_users.rb
|
157
161
|
- test_app/db/migrate/20111217215935_add_birth_date_to_users.rb
|
158
162
|
- test_app/db/migrate/20111224181356_add_money_to_user.rb
|
159
163
|
- test_app/db/schema.rb
|
@@ -188,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
188
192
|
version: '0'
|
189
193
|
segments:
|
190
194
|
- 0
|
191
|
-
hash: -
|
195
|
+
hash: -572262513
|
192
196
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
197
|
none: false
|
194
198
|
requirements:
|
@@ -197,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
201
|
version: '0'
|
198
202
|
segments:
|
199
203
|
- 0
|
200
|
-
hash: -
|
204
|
+
hash: -572262513
|
201
205
|
requirements: []
|
202
206
|
rubyforge_project: best_in_place
|
203
207
|
rubygems_version: 1.8.10
|