best_in_place 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/README.md +270 -51
- data/lib/assets/javascripts/best_in_place.js +20 -3
- data/lib/best_in_place.rb +2 -0
- data/lib/best_in_place/controller_extensions.rb +23 -0
- data/lib/best_in_place/display_methods.rb +16 -0
- data/lib/best_in_place/engine.rb +1 -0
- data/lib/best_in_place/helper.rb +16 -3
- data/lib/best_in_place/version.rb +1 -1
- data/spec/helpers/best_in_place_spec.rb +23 -1
- data/spec/integration/js_spec.rb +46 -0
- data/test_app/Gemfile +2 -0
- data/test_app/app/controllers/users_controller.rb +2 -2
- data/test_app/app/models/user.rb +9 -1
- data/test_app/app/views/users/show.html.erb +2 -2
- metadata +16 -14
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -26,11 +26,13 @@ The editor works by PUTting the updated value to the server and GETting the upda
|
|
26
26
|
- Allows external activator
|
27
27
|
- ESC key destroys changes (requires user confirmation)
|
28
28
|
- Autogrowing textarea
|
29
|
-
|
30
|
-
|
29
|
+
- Helper for generating the best_in_place field only if a condition is satisfied
|
30
|
+
- Provided test helpers to be used in your integration specs
|
31
|
+
- Custom display methods
|
31
32
|
|
32
33
|
##Usage of Rails 3 Gem
|
33
34
|
|
35
|
+
###best_in_place
|
34
36
|
**best_in_place object, field, OPTIONS**
|
35
37
|
|
36
38
|
Params:
|
@@ -48,10 +50,38 @@ Options:
|
|
48
50
|
If not defined it will show *"-"*.
|
49
51
|
- **:activator**: Is the DOM object that can activate the field. If not defined the user will making editable by clicking on it.
|
50
52
|
- **:sanitize**: True by default. If set to false the input/textarea will accept html tags.
|
51
|
-
- **:
|
53
|
+
- **:html_attrs**: Hash of html arguments, such as maxlength, default-value etc.
|
54
|
+
- **:inner_class**: Class that is set to the rendered form.
|
55
|
+
- **:display_as**: A model method which will be called in order to display
|
56
|
+
this field.
|
57
|
+
|
58
|
+
###best_in_place_if
|
59
|
+
**best_in_place_if condition, object, field, OPTIONS**
|
60
|
+
|
61
|
+
It allows us to use best_in_place only if the first new parameter, a
|
62
|
+
condition, is satisfied. Specifically:
|
63
|
+
|
64
|
+
* Will show a normal best_in_place if the condition is satisfied
|
65
|
+
* Will only show the attribute from the instance if the condition is not satisfied
|
66
|
+
|
67
|
+
Say we have something like
|
68
|
+
|
69
|
+
<%= best_in_place condition, @user, :name, :type => :input %>
|
70
|
+
|
71
|
+
In case *condition* is satisfied, the outcome will be just the same as:
|
72
|
+
|
73
|
+
<%= best_in_place @user, :name, :type => :input %>
|
74
|
+
|
75
|
+
Otherwise, we will have the same outcome as:
|
76
|
+
|
77
|
+
<%= @user.name %>
|
52
78
|
|
79
|
+
It is a very useful feature to use with, for example, [Ryan Bates](https://github.com/ryanb)' [CanCan](https://github.com/ryanb/cancan), so we only allow BIP edition if the current user has permission to do it.
|
53
80
|
|
54
|
-
|
81
|
+
---
|
82
|
+
|
83
|
+
##TestApp and examples
|
84
|
+
A [test_app](https://github.com/bernat/best_in_place/tree/master/test_app) was created, and can be seen in action in a [running demo on heroku](http://bipapp.heroku.com).
|
55
85
|
|
56
86
|
Examples (code in the views):
|
57
87
|
|
@@ -69,83 +99,230 @@ Examples (code in the views):
|
|
69
99
|
|
70
100
|
<%= best_in_place @user, :country, :type => :select, :collection => [[1, "Spain"], [2, "Italy"], [3, "Germany"], [4, "France"]] %>
|
71
101
|
|
72
|
-
Of course it can take an instance or global variable for the collection, just remember the structure [[key, value], [key, value],...]
|
102
|
+
Of course it can take an instance or global variable for the collection, just remember the structure `[[key, value], [key, value],...]`.
|
73
103
|
The key can be a string or an integer.
|
74
104
|
|
75
105
|
### Checkbox
|
76
106
|
|
77
107
|
<%= best_in_place @user, :receive_emails, :type => :checkbox, :collection => ["No, thanks", "Yes, of course!"] %>
|
78
108
|
|
79
|
-
The first value is always the negative boolean value and the second the positive. Structure: ["false value", "true value"]
|
109
|
+
The first value is always the negative boolean value and the second the positive. Structure: `["false value", "true value"]`.
|
80
110
|
If not defined, it will default to *Yes* and *No* options.
|
81
111
|
|
82
|
-
|
112
|
+
## Controller response and respond_with_bip
|
83
113
|
|
84
|
-
|
85
|
-
|
114
|
+
Your controller should respond to json as it's the format used by best in
|
115
|
+
place javascript. A simple example would be:
|
86
116
|
|
87
|
-
|
88
|
-
|
117
|
+
class UserController < ApplicationController
|
118
|
+
def update
|
119
|
+
@user = User.find(params[:id])
|
89
120
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
121
|
+
respond_to do |format|
|
122
|
+
if @user.update_attributes(params[:user])
|
123
|
+
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
|
124
|
+
format.json { head :ok }
|
125
|
+
else
|
126
|
+
format.html { render :action => "edit" }
|
127
|
+
format.json { render :json => @user.errors.full_messages, :status => :unprocessable_entity }
|
128
|
+
end
|
97
129
|
end
|
98
130
|
end
|
99
131
|
end
|
100
132
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
133
|
+
If you respond with a json like `{:display_as => "New value to show"}` with
|
134
|
+
status 200 (ok), then the updated field will show *New value to show* after
|
135
|
+
being updated. This is needed in order to support the custom display methods,
|
136
|
+
and it's automatically handled if you use the new method to encapsulate
|
137
|
+
the responses:
|
138
|
+
|
139
|
+
|
140
|
+
respond_to do |format|
|
141
|
+
if @user.update_attributes(params[:user])
|
142
|
+
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
|
143
|
+
format.json { respond_with_bip(@user) }
|
144
|
+
else
|
145
|
+
format.html { render :action => "edit" }
|
146
|
+
format.json { respond_with_bip(@user) }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
This will be exactly the same as the previous example, but with support to
|
151
|
+
handle custom display methods.
|
152
|
+
|
153
|
+
##Using custom display methods
|
154
|
+
|
155
|
+
As of best in place 1.0.3 you can use custom methods in your model in order to
|
156
|
+
decide how a certain field has to be displayed. You can write something like:
|
157
|
+
|
158
|
+
= best_in_place @user, :description, :type => :textarea, :display_as => :mk_description
|
159
|
+
|
160
|
+
Then instead of using `@user.description` to show the actual value, best in
|
161
|
+
place will call `@user.mk_description`. This can be used for any kind of
|
162
|
+
custom formatting, text with markdown, currency values, etc...
|
163
|
+
|
164
|
+
Because best in place has no way to call that method in your model from
|
165
|
+
javascript after a successful update, the only way to display the new correct
|
166
|
+
value after an edition is to use the provided methods to respond in your
|
167
|
+
controllers, or implement the same in your own way.
|
168
|
+
|
169
|
+
If you respond a successful update with a json having a `display_as` key, that
|
170
|
+
value will be used to update the value in the view. The provided
|
171
|
+
`respond_with_bip` handles this for you, but if you want you can always
|
172
|
+
customize this behaviour.
|
173
|
+
|
174
|
+
|
175
|
+
##Non Active Record environments
|
176
|
+
We are not planning to support other ORMs apart from Active Record, at least for now. So, you can perfectly consider the following workaround as *the right way* until a specific implementation is done for your ORM.
|
177
|
+
|
178
|
+
Best In Place automatically assumes that Active Record is the ORM you are using. However, this might not be your case, as you might use another ORM (or not ORM at all for that case!). Good news for you: even in such situation Best In Place can be used!
|
179
|
+
|
180
|
+
Let's setup an example so we can illustrate how to use Best In Place too in a non-ORM case. Imagine you have an awesome ice cream shop, and you have a model representing a single type of ice cream. The IceCream model has a name, a description, a... nevermind. The thing is that it also has a stock, which is a combination of flavour and size. A big chocolate ice cream (yummy!), a small paella ice cream (...really?), and so on. Shall we see some code?
|
181
|
+
|
182
|
+
class IceCream < ActiveRecord::Base
|
183
|
+
serialize :stock, Hash
|
184
|
+
|
185
|
+
# consider the get_stock and set_stock methods are already defined
|
117
186
|
end
|
118
187
|
|
119
|
-
|
188
|
+
Imagine we want to have a grid showing all the combinations of flavour and size and, for each combination, an editable stock. Since the stock for a flavour and a size is not a single and complete model attribute, we cannot use Best In Place *directly*. But we can set it up with an easy workaround.
|
189
|
+
|
190
|
+
In the view, we'd do:
|
191
|
+
|
192
|
+
// @ice_cream is already available
|
193
|
+
- flavours = ... // get them somewhere
|
194
|
+
- sizes = ... // get them somewhere
|
195
|
+
%table
|
196
|
+
%tr
|
197
|
+
- ([""] + flavours).each do |flavour|
|
198
|
+
%th= flavour
|
199
|
+
- sizes.each do |size|
|
200
|
+
%tr
|
201
|
+
%th= size
|
202
|
+
- flavours.each do |flavour|
|
203
|
+
- v = @ice_cream.get_stock(:flavour => flavour, :size => size)
|
204
|
+
%td= best_in_place v, :to_i, :type => :input, :path => set_stock_ice_cream_path(:flavour => flavour, :size => size)
|
205
|
+
|
206
|
+
Now we need a route to which send the stock updates:
|
207
|
+
|
208
|
+
TheAwesomeIceCreamShop::Application.routes.draw do
|
209
|
+
...
|
210
|
+
|
211
|
+
resources :ice_creams, :only => :none do
|
212
|
+
member do
|
213
|
+
put :set_stock
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
...
|
218
|
+
end
|
219
|
+
|
220
|
+
And finally we need a controller:
|
221
|
+
|
222
|
+
|
223
|
+
class IceCreamsController < ApplicationController::Base
|
224
|
+
respond_to :html, :json
|
225
|
+
|
226
|
+
...
|
227
|
+
|
228
|
+
def set_stock
|
229
|
+
flavour = params[:flavour]
|
230
|
+
size = params[:size]
|
231
|
+
new_stock = (params["fixnum"] || {})["to_i"]
|
232
|
+
|
233
|
+
@ice_cream.set_stock(new_stock, { :flavour => flavour, :size => size })
|
234
|
+
if @ice_cream.save
|
235
|
+
head :ok
|
236
|
+
else
|
237
|
+
render :json => @ice_cream.errors.full_messages, :status => :unprocessable_entity
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
...
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
And this is how it is done!
|
246
|
+
|
247
|
+
---
|
248
|
+
|
249
|
+
##Test Helpers
|
250
|
+
Best In Place has also some helpers that may be very useful for integration testing. Since it might very common to test some views using Best In Place, some helpers are provided to ease it.
|
251
|
+
|
252
|
+
As of now, a total of four helpers are available. There is one for each of the following BIP types: a plain text input, a textarea, a boolean input and a selector. Its function is to simulate the user's action of filling such fields.
|
253
|
+
|
254
|
+
These four helpers are listed below:
|
255
|
+
|
256
|
+
* **bip_area(model, attr, new_value)**
|
257
|
+
* **bip_text(model, attr, new_value)**
|
258
|
+
* **bip_bool(model, attr)**
|
259
|
+
* **bip_select(model, attr, name)**
|
260
|
+
|
261
|
+
The parameters are defined here (some are method-specific):
|
262
|
+
|
263
|
+
* **model**: the model to which this action applies.
|
264
|
+
* **attr**: the attribute of the model to which this action applies.
|
265
|
+
* **new_value** (only **bip_area** and **bip_text**): the new value with which to fill the BIP field.
|
266
|
+
* **name** (only **bip_select**): the name to select from the dropdown selector.
|
120
267
|
|
121
268
|
---
|
122
269
|
|
123
270
|
##Installation
|
124
271
|
|
125
|
-
|
126
|
-
in the following order:
|
272
|
+
###Rails 3.1 and higher
|
127
273
|
|
128
|
-
-
|
129
|
-
|
130
|
-
- **best_in_place.js**
|
274
|
+
Installing *best_in_place* is very easy and straight-forward, even more
|
275
|
+
thanks to Rails 3.1. Just begin including the gem in your Gemfile:
|
131
276
|
|
132
|
-
|
133
|
-
Remember to do it every time you update the gem (or you will see no change).
|
277
|
+
gem "best_in_place"
|
134
278
|
|
135
|
-
|
279
|
+
After that, specify the use of the jquery, jquery.purr and best in place
|
280
|
+
javascripts in your application.js:
|
281
|
+
|
282
|
+
//= require jquery
|
283
|
+
//= require jquery.purr
|
284
|
+
//= require best_in_place
|
136
285
|
|
137
|
-
|
286
|
+
Then, just add a binding to prepare all best in place fields when the document is ready:
|
138
287
|
|
139
288
|
$(document).ready(function() {
|
140
289
|
/* Activating Best In Place */
|
141
|
-
jQuery(".best_in_place").best_in_place()
|
290
|
+
jQuery(".best_in_place").best_in_place();
|
142
291
|
});
|
143
292
|
|
144
|
-
|
293
|
+
You are done!
|
145
294
|
|
146
|
-
|
295
|
+
###Rails 3.0 and lower
|
296
|
+
|
297
|
+
Installing *best_in_place* for Rails 3.0 or below is a little bit
|
298
|
+
different, since the master branch is specifically updated for Rails
|
299
|
+
3.1. But don't be scared, you'll be fine!
|
300
|
+
|
301
|
+
Rails 3.0 support will be held in the 0.2.X versions, but we have planned not to continue developing for this version of Rails. Nevertheless, you can by implementing what you want and sending us a pull request.
|
302
|
+
|
303
|
+
First, add the gem's 0.2 version in the Gemfile:
|
304
|
+
|
305
|
+
gem "best_in_place", "~> 0.2.0"
|
306
|
+
|
307
|
+
After that, install and load all the javascripts from the folder
|
308
|
+
**/public/javascripts** in your layouts. They have to be in the order:
|
309
|
+
|
310
|
+
* jquery
|
311
|
+
* jquery.purr
|
312
|
+
* **best_in_place**
|
313
|
+
|
314
|
+
You can automatize this installation by doing
|
147
315
|
|
148
|
-
|
316
|
+
rails g best_in_place:setup
|
317
|
+
|
318
|
+
Finally, as for Rails 3.1, just add a binding to prepare all best in place fields when the document is ready:
|
319
|
+
|
320
|
+
$(document).ready(function() {
|
321
|
+
/* Activating Best In Place */
|
322
|
+
jQuery(".best_in_place").best_in_place();
|
323
|
+
});
|
324
|
+
|
325
|
+
---
|
149
326
|
|
150
327
|
## Security
|
151
328
|
|
@@ -154,17 +331,45 @@ If the script is used with the Rails Gem no html tags will be allowed unless the
|
|
154
331
|
<meta name="csrf-param" content="authenticity_token"/>
|
155
332
|
<meta name="csrf-token" content="YOUR UNIQUE TOKEN HERE"/>
|
156
333
|
|
334
|
+
---
|
335
|
+
|
157
336
|
##TODO
|
158
337
|
|
159
338
|
- Client Side Validation definitions
|
160
339
|
- Accepting more than one handler to activate best_in_place fields
|
161
|
-
|
340
|
+
|
341
|
+
---
|
342
|
+
|
343
|
+
## Development
|
344
|
+
|
345
|
+
Fork the project on [github](https://github.com/bernat/best_in_place 'bernat / best_in_place on Github')
|
346
|
+
|
347
|
+
$ git clone <<your fork>
|
348
|
+
$ cd best_in_place
|
349
|
+
$ bundle
|
350
|
+
|
351
|
+
### Prepare the test app
|
352
|
+
|
353
|
+
$ cd test_app
|
354
|
+
$ bundle
|
355
|
+
$ bundle exec rake db:test:prepare
|
356
|
+
$ cd ..
|
357
|
+
|
358
|
+
### Run the specs
|
359
|
+
|
360
|
+
$ bundle exec rspec spec/
|
361
|
+
|
362
|
+
### Bundler / gem troubleshooting
|
363
|
+
|
364
|
+
- make sure you've run the bundle command for both the app and test_app!
|
365
|
+
- run bundle update <<gem name> (in the right place) for any gems that are causing issues
|
162
366
|
|
163
367
|
---
|
164
368
|
|
165
369
|
##Changelog
|
166
370
|
|
167
|
-
|
371
|
+
###Master branch (and part of the Rails 3.0 branch)
|
372
|
+
- v.0.1.0 Initial commit
|
168
373
|
- v.0.1.2 Fixing errors in collections (taken value[0] instead of index) and fixing test_app controller responses
|
169
374
|
- v.0.1.3 Bug in Rails Helper. Key wrongly considered an Integer.
|
170
375
|
- v.0.1.4 Adding two new parameters for further customization urlObject and nilValue and making input update on blur.
|
@@ -173,9 +378,23 @@ If the script is used with the Rails Gem no html tags will be allowed unless the
|
|
173
378
|
of key ESCAPE for destroying changes before they are made permanent (in inputs and textarea).
|
174
379
|
- v.0.1.6-0.1.7 Avoiding request when the input is not modified and allowing the user to not sanitize input data.
|
175
380
|
- v.0.1.8 jslint compliant, sanitizing tags in the gem, getting right csrf params, controlling size of textarea (elastic script, for autogrowing textarea)
|
381
|
+
- v.0.1.9 Adding elastic autogrowing textareas
|
382
|
+
- v.1.0.0 Setting RSpec and Capybara up, and adding some utilities. Mantaining some HTML attributes. Fix a respond_with bug (thanks, @moabite). Triggering ajax:success when ajax call is complete (thanks, @indrekj). Setting up Travis CI. Updated for Rails 3.1.
|
383
|
+
- v.1.0.1 Fixing a double initialization bug
|
384
|
+
- v.1.0.2 New bip_area text helper to work with text areas.
|
385
|
+
- v.1.0.3 replace apostrophes in collection with corresponding HTML entity,
|
386
|
+
thanks @taavo. Implemented `:display_as` option and adding
|
387
|
+
`respond_with_bip` to be used in the controller.
|
388
|
+
|
389
|
+
###Rails 3.0 branch only
|
390
|
+
- v.0.2.0 Added RSpec and Capybara setup, and some tests. Fix countries map syntax, Allowing href and some other HTML attributes. Adding Travis CI too. Added the best_in_place_if option. Added ajax:success trigger, thanks to @indrekj.
|
391
|
+
- v.0.2.1 Fixing double initialization bug.
|
392
|
+
- v.0.2.2 New bip_area text helper.
|
393
|
+
|
394
|
+
---
|
176
395
|
|
177
396
|
##Authors, License and Stuff
|
178
397
|
|
179
398
|
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).
|
180
399
|
|
181
|
-
Many thanks to the contributors: [Roger Campos](http://github.com/rogercampos)
|
400
|
+
Many thanks to the contributors: [Roger Campos](http://github.com/rogercampos), [Jack Senechal](https://github.com/jacksenechal) and [Albert Bellonch](https://github.com/albertbellonch).
|
@@ -31,8 +31,20 @@ BestInPlaceEditor.prototype = {
|
|
31
31
|
// Public Interface Functions //////////////////////////////////////////////
|
32
32
|
|
33
33
|
activate : function() {
|
34
|
+
var to_display = "";
|
35
|
+
if (this.isNil) {
|
36
|
+
to_display = "";
|
37
|
+
}
|
38
|
+
else if (this.original_content) {
|
39
|
+
to_display = this.original_content;
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
to_display = this.element.html();
|
43
|
+
}
|
44
|
+
|
34
45
|
var elem = this.isNil ? "" : this.element.html();
|
35
46
|
this.oldValue = elem;
|
47
|
+
this.display_value = to_display;
|
36
48
|
$(this.activator).unbind("click", this.clickHandler);
|
37
49
|
this.activateForm();
|
38
50
|
},
|
@@ -91,6 +103,7 @@ BestInPlaceEditor.prototype = {
|
|
91
103
|
self.nil = self.nil || jQuery(this).attr("data-nil");
|
92
104
|
self.inner_class = self.inner_class || jQuery(this).attr("data-inner-class");
|
93
105
|
self.html_attrs = self.html_attrs || jQuery(this).attr("data-html-attrs");
|
106
|
+
self.original_content = self.original_content || jQuery(this).attr("data-original-content");
|
94
107
|
});
|
95
108
|
|
96
109
|
// Try Rails-id based if parents did not explicitly supply something
|
@@ -111,6 +124,7 @@ BestInPlaceEditor.prototype = {
|
|
111
124
|
self.nil = self.element.attr("data-nil") || self.nil || "-";
|
112
125
|
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
113
126
|
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
127
|
+
self.original_content = self.element.attr("data-original-content") || self.original_content;
|
114
128
|
|
115
129
|
if (!self.element.attr("data-sanitize")) {
|
116
130
|
self.sanitize = true;
|
@@ -177,7 +191,10 @@ BestInPlaceEditor.prototype = {
|
|
177
191
|
// Handlers ////////////////////////////////////////////////////////////////
|
178
192
|
|
179
193
|
loadSuccessCallback : function(data) {
|
180
|
-
|
194
|
+
var response = $.parseJSON($.trim(data));
|
195
|
+
if (response != null && response.hasOwnProperty("display_as")) {
|
196
|
+
this.element.html(response["display_as"]);
|
197
|
+
}
|
181
198
|
this.element.trigger($.Event("ajax:success"), data);
|
182
199
|
|
183
200
|
// Binding back after being clicked
|
@@ -216,7 +233,7 @@ BestInPlaceEditor.forms = {
|
|
216
233
|
"input" : {
|
217
234
|
activateForm : function() {
|
218
235
|
var output = '<form class="form_in_place" action="javascript:void(0)" style="display:inline;">';
|
219
|
-
output += '<input type="text" name="'+ this.attributeName + '" value="' + this.sanitizeValue(this.
|
236
|
+
output += '<input type="text" name="'+ this.attributeName + '" value="' + this.sanitizeValue(this.display_value) + '"';
|
220
237
|
if (this.inner_class != null) {
|
221
238
|
output += ' class="' + this.inner_class + '"';
|
222
239
|
}
|
@@ -301,7 +318,7 @@ BestInPlaceEditor.forms = {
|
|
301
318
|
|
302
319
|
// construct the form
|
303
320
|
var output = '<form action="javascript:void(0)" style="display:inline;"><textarea>';
|
304
|
-
output += this.sanitizeValue(this.
|
321
|
+
output += this.sanitizeValue(this.display_value);
|
305
322
|
output += '</textarea></form>';
|
306
323
|
this.element.html(output);
|
307
324
|
this.setHtmlAttributes();
|
data/lib/best_in_place.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
module BestInPlace
|
2
|
+
module ControllerExtensions
|
3
|
+
def respond_with_bip(obj)
|
4
|
+
obj.changed? ? respond_bip_error(obj) : respond_bip_ok(obj)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
def respond_bip_ok(obj)
|
9
|
+
klass = obj.class.to_s
|
10
|
+
updating_attr = params[klass.underscore].keys.first
|
11
|
+
|
12
|
+
if renderer = BestInPlace::DisplayMethods.lookup(klass, updating_attr)
|
13
|
+
render :json => {:display_as => obj.send(renderer)}.to_json
|
14
|
+
else
|
15
|
+
head :ok
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_bip_error(obj)
|
20
|
+
render :json => obj.errors.full_messages, :status => :unprocessable_entity
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module BestInPlace
|
2
|
+
module DisplayMethods
|
3
|
+
extend self
|
4
|
+
|
5
|
+
@@table = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
|
6
|
+
|
7
|
+
def lookup(klass, attr)
|
8
|
+
foo = @@table[klass.to_s][attr.to_s]
|
9
|
+
foo == {} ? nil : foo
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(klass, attr, display_as)
|
13
|
+
@@table[klass.to_s][attr.to_s] = display_as.to_sym
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/best_in_place/engine.rb
CHANGED
data/lib/best_in_place/helper.rb
CHANGED
@@ -5,7 +5,9 @@ module BestInPlace
|
|
5
5
|
opts[:type] ||= :input
|
6
6
|
opts[:collection] ||= []
|
7
7
|
field = field.to_s
|
8
|
-
|
8
|
+
|
9
|
+
value = build_value_for(object, field, opts)
|
10
|
+
|
9
11
|
collection = nil
|
10
12
|
if opts[:type] == :select && !opts[:collection].blank?
|
11
13
|
v = object.send(field)
|
@@ -24,13 +26,14 @@ module BestInPlace
|
|
24
26
|
out << " id='#{BestInPlace::Utils.build_best_in_place_id(object, field)}'"
|
25
27
|
out << " data-url='#{opts[:path].blank? ? url_for(object).to_s : url_for(opts[:path])}'"
|
26
28
|
out << " data-object='#{object.class.to_s.gsub("::", "_").underscore}'"
|
27
|
-
out << " data-collection='#{collection}'" unless collection.blank?
|
29
|
+
out << " data-collection='#{collection.gsub(/'/, "'")}'" unless collection.blank?
|
28
30
|
out << " data-attribute='#{field}'"
|
29
31
|
out << " data-activator='#{opts[:activator]}'" unless opts[:activator].blank?
|
30
32
|
out << " data-nil='#{opts[:nil].to_s}'" unless opts[:nil].blank?
|
31
33
|
out << " data-type='#{opts[:type].to_s}'"
|
32
34
|
out << " data-inner-class='#{opts[:inner_class].to_s}'" if opts[:inner_class]
|
33
35
|
out << " data-html-attrs='#{opts[:html_attrs].to_json}'" unless opts[:html_attrs].blank?
|
36
|
+
out << " data-original-content='#{object.send(field)}'" if opts[:display_as]
|
34
37
|
if !opts[:sanitize].nil? && !opts[:sanitize]
|
35
38
|
out << " data-sanitize='false'>"
|
36
39
|
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))
|
@@ -45,7 +48,17 @@ module BestInPlace
|
|
45
48
|
if condition
|
46
49
|
best_in_place(object, field, opts)
|
47
50
|
else
|
48
|
-
object
|
51
|
+
build_value_for object, field, opts
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def build_value_for(object, field, opts)
|
57
|
+
if opts[:display_as]
|
58
|
+
BestInPlace::DisplayMethods.add(object.class.to_s, field, opts[:display_as])
|
59
|
+
object.send(opts[:display_as]).to_s
|
60
|
+
else
|
61
|
+
object.send(field).blank? ? "" : object.send(field).to_s
|
49
62
|
end
|
50
63
|
end
|
51
64
|
end
|
@@ -123,6 +123,16 @@ describe BestInPlace::BestInPlaceHelpers do
|
|
123
123
|
span = nk.css("span")
|
124
124
|
span.attribute("data-activator").value.should == "awesome"
|
125
125
|
end
|
126
|
+
|
127
|
+
describe "display_as" do
|
128
|
+
it "should render the address with a custom renderer" do
|
129
|
+
@user.should_receive(:address_format).and_return("the result")
|
130
|
+
out = helper.best_in_place @user, :address, :display_as => :address_format
|
131
|
+
nk = Nokogiri::HTML.parse(out)
|
132
|
+
span = nk.css("span")
|
133
|
+
span.text.should == "the result"
|
134
|
+
end
|
135
|
+
end
|
126
136
|
end
|
127
137
|
|
128
138
|
|
@@ -199,6 +209,18 @@ describe BestInPlace::BestInPlaceHelpers do
|
|
199
209
|
it "should show the current country" do
|
200
210
|
@span.text.should == "Italy"
|
201
211
|
end
|
212
|
+
|
213
|
+
context "with an apostrophe in it" do
|
214
|
+
before do
|
215
|
+
@apostrophe_countries = [[1, "Joe's Country"], [2, "Bob's Country"]]
|
216
|
+
nk = Nokogiri::HTML.parse(helper.best_in_place @user, :country, :type => :select, :collection => @apostrophe_countries)
|
217
|
+
@span = nk.css("span")
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should have a proper data collection" do
|
221
|
+
@span.attribute("data-collection").value.should == @apostrophe_countries.to_json
|
222
|
+
end
|
223
|
+
end
|
202
224
|
end
|
203
225
|
end
|
204
226
|
|
@@ -208,7 +230,7 @@ describe BestInPlace::BestInPlaceHelpers do
|
|
208
230
|
@output = "Some Value"
|
209
231
|
@field = :somefield
|
210
232
|
@object = mock("object", @field => @output)
|
211
|
-
@options =
|
233
|
+
@options = {}
|
212
234
|
end
|
213
235
|
context "when the condition is true" do
|
214
236
|
before {@condition = true}
|
data/spec/integration/js_spec.rb
CHANGED
@@ -161,5 +161,51 @@ describe "JS behaviour", :js => true do
|
|
161
161
|
page.should have_content("Another")
|
162
162
|
end
|
163
163
|
end
|
164
|
+
|
165
|
+
describe "display_as" do
|
166
|
+
it "should render the address with a custom format" do
|
167
|
+
@user.save!
|
168
|
+
visit user_path(@user)
|
169
|
+
|
170
|
+
within("#address") do
|
171
|
+
page.should have_content("addr => [Via Roma 99]")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should still show the custom format after an error" do
|
176
|
+
@user.save!
|
177
|
+
visit user_path(@user)
|
178
|
+
|
179
|
+
bip_text @user, :address, "inva"
|
180
|
+
|
181
|
+
within("#address") do
|
182
|
+
page.should have_content("addr => [Via Roma 99]")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should show the new result with the custom format after an update" do
|
187
|
+
@user.save!
|
188
|
+
visit user_path(@user)
|
189
|
+
|
190
|
+
bip_text @user, :address, "New address"
|
191
|
+
|
192
|
+
within("#address") do
|
193
|
+
page.should have_content("addr => [New address]")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should display the original content when editing the form" do
|
198
|
+
@user.save!
|
199
|
+
visit user_path(@user)
|
200
|
+
|
201
|
+
id = BestInPlace::Utils.build_best_in_place_id @user, :address
|
202
|
+
page.execute_script <<-JS
|
203
|
+
$("##{id}").click();
|
204
|
+
JS
|
205
|
+
|
206
|
+
text = page.find("##{id} input").value
|
207
|
+
text.should == "Via Roma 99"
|
208
|
+
end
|
209
|
+
end
|
164
210
|
end
|
165
211
|
|
data/test_app/Gemfile
CHANGED
@@ -69,10 +69,10 @@ class UsersController < ApplicationController
|
|
69
69
|
respond_to do |format|
|
70
70
|
if @user.update_attributes(params[:user])
|
71
71
|
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
|
72
|
-
format.json {
|
72
|
+
format.json { respond_with_bip(@user) }
|
73
73
|
else
|
74
74
|
format.html { render :action => "edit" }
|
75
|
-
format.json
|
75
|
+
format.json { respond_with_bip(@user) }
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
data/test_app/app/models/user.rb
CHANGED
@@ -12,4 +12,12 @@ class User < ActiveRecord::Base
|
|
12
12
|
:presence => {:message => "can't be blank"},
|
13
13
|
:format => {:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "has wrong email format"}
|
14
14
|
validates :zip, :numericality => true, :length => { :minimum => 5 }
|
15
|
-
|
15
|
+
|
16
|
+
def address_format
|
17
|
+
"<b>addr => [#{address}]</b>".html_safe
|
18
|
+
end
|
19
|
+
|
20
|
+
def markdown_desc
|
21
|
+
RDiscount.new(description).to_html.html_safe
|
22
|
+
end
|
23
|
+
end
|
@@ -25,7 +25,7 @@
|
|
25
25
|
<tr>
|
26
26
|
<td>Address</td>
|
27
27
|
<td id="address">
|
28
|
-
<%= best_in_place @user, :address %>
|
28
|
+
<%= best_in_place @user, :address, :display_as => :address_format %>
|
29
29
|
</td>
|
30
30
|
</tr>
|
31
31
|
<tr>
|
@@ -49,7 +49,7 @@
|
|
49
49
|
<tr>
|
50
50
|
<td>User description</td>
|
51
51
|
<td id="description">
|
52
|
-
<%= best_in_place @user, :description, :type => :textarea, :sanitize => false %>
|
52
|
+
<%= best_in_place @user, :description, :display_as => :markdown_desc, :type => :textarea, :sanitize => false %>
|
53
53
|
</td>
|
54
54
|
</table>
|
55
55
|
<br />
|
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.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-12-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &12601240 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.1.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *12601240
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: jquery-rails
|
27
|
-
requirement: &
|
27
|
+
requirement: &12616680 !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: *12616680
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec-rails
|
38
|
-
requirement: &
|
38
|
+
requirement: &12615660 !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: *12615660
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: nokogiri
|
49
|
-
requirement: &
|
49
|
+
requirement: &12614960 !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: *12614960
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: capybara
|
60
|
-
requirement: &
|
60
|
+
requirement: &12614300 !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: *12614300
|
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,6 +86,8 @@ 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/controller_extensions.rb
|
90
|
+
- lib/best_in_place/display_methods.rb
|
89
91
|
- lib/best_in_place/engine.rb
|
90
92
|
- lib/best_in_place/helper.rb
|
91
93
|
- lib/best_in_place/test_helpers.rb
|
@@ -167,7 +169,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
169
|
version: '0'
|
168
170
|
segments:
|
169
171
|
- 0
|
170
|
-
hash:
|
172
|
+
hash: 1789915415382083168
|
171
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
174
|
none: false
|
173
175
|
requirements:
|
@@ -176,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
178
|
version: '0'
|
177
179
|
segments:
|
178
180
|
- 0
|
179
|
-
hash:
|
181
|
+
hash: 1789915415382083168
|
180
182
|
requirements: []
|
181
183
|
rubyforge_project: best_in_place
|
182
184
|
rubygems_version: 1.8.10
|