safe_in_place_editing 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 384a023dadf89e878b381e57b2c352ecd7317916
4
+ data.tar.gz: 7ef531b3a8ee0bbeb7ea6c7ce983f0de1593cf5d
5
+ SHA512:
6
+ metadata.gz: 21f0dedc4d6d5643730e53bb73569e0657b8ec39e05c1d1a7b4e0d61cc17b2e50a9f39e97aba73d33b08b4e8b4ed49f726798dbdce7d12aa36571815caa17149
7
+ data.tar.gz: 620f9756d1595728dc2e9665e51e99788eaddb8021a615032c9681fab3139a45b2ece720e032e43d1d4f02b86059d960be7a3215e47bb4891e668126195ae7ea
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Hipposoft
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,318 @@
1
+ = Safe In Place Editing v2.00 (2013-10-22)
2
+
3
+ Early versions of Ruby On Rails shipped with an in-place editing mechanism
4
+ which allowed simple edits to be performed on model attribute values without
5
+ having to render a full 'edit' view for that model.
6
+
7
+ Unfortunately this suffered a significant vulnerability which basically meant
8
+ that any in-place editor field was an open JavaScript console, much to the
9
+ presumed delight of malicious users. In-place editing features were later moved
10
+ into a plug-in and the cross-site scripting fault was fixed; this simple plugin
11
+ is available from GitHub:
12
+
13
+ * https://github.com/amerine/in_place_editing
14
+
15
+ The original version's vulnerability compelled me to write a replacement. Even
16
+ after the original was fixed, it still had some shortcomings and the Safe In
17
+ Place Editing code addresses those. The full rationale for its creation, along
18
+ with instructions on its use are given below.
19
+
20
+ Version 1.03 of this software is designed for Rails 3, which deprecates plug-in
21
+ code. Installation is therefore a bit more complex than running "script/plugin"
22
+ as under Rails 2. Such is progress, it seems...!
23
+
24
+ This plug-in would not exist without the original in-place editor code,
25
+ from which it borrows very heavily. My thanks to all involved.
26
+
27
+
28
+ == Upgrading
29
+
30
+ If you were using the plug-in version of Safe In Place Editing, you need to
31
+ delete the old Safe In Place Editing code from inside "vendor/plugins" in your
32
+ app. You will also need to modify the way you include the support JavaScript
33
+ code from this:
34
+
35
+ <%= javascript_include_tag( "safe_in_place_editing/safe_in_place_editing" ) %>
36
+
37
+ ...to this:
38
+
39
+ <%= javascript_include_tag( "safe_in_place_editing" ) %>
40
+
41
+ A global search and replace of "safe_in_place_editing/safe_in_place_editing/"
42
+ to just "safe_in_place_editing" across your application files may suffice.
43
+
44
+ Now follow the steps given in "Installation" below, to install the new Gem
45
+ version of Safe In Place Editing.
46
+
47
+
48
+ == Installation
49
+
50
+ Add this line to your application's Gemfile:
51
+
52
+ gem 'safe_in_place_editing'
53
+
54
+ You will need to use the Prototype JavaScript library rather than jQuery (the
55
+ two may coexist peacefully, but I haven't tested it). To use that, add the
56
+ following to your Gemfile, if you don't already have it:
57
+
58
+ gem 'prototype-rails'
59
+
60
+ You can find out more about proper usage of the JavaScript files provided by
61
+ this Gem here:
62
+
63
+ * https://github.com/rails/prototype-rails
64
+
65
+ Finally, execute:
66
+
67
+ bundle
68
+
69
+ Please note that the Gem here has only been tested in Rails 3 applications
70
+ that use the Rails Asset Pipeline, so though it might work on (for example)
71
+ legacy upgraded Rails 2 applications that don't use the asset pipeline file
72
+ structure, I can't be sure. If you encounter problems, consider switching
73
+ over to the pipeline instead.
74
+
75
+ * http://guides.rubyonrails.org/asset_pipeline.html
76
+
77
+ See "Usage exmaples" later for information on how to use the Gem.
78
+
79
+
80
+ == InPlaceEditing problems and Safe In Place Editing solutions
81
+ === 1: InPlaceEditing's XSS fixes prevent other features from working
82
+
83
+ At the time of writing the original in-place-editor plugin escapes values
84
+ written into the view coming from the database and values written into the
85
+ view after a user makes an edit, avoiding the vulnerability which it used
86
+ to introduce. This introduces some problems though:
87
+
88
+ 1. It's no longer possible to deliberately insert HTML at all, as per older
89
+ documentation examples for Textile and Markaby-based in-place editors.
90
+
91
+ 2. The in-place editor form, when shown, shows the _literal_ escaped text (so
92
+ you get double-escape problems, with things like "&amp;" showing up instead
93
+ of "&") because it uses a "getText" call in JavaScript that reads
94
+ "innerHTML" of the <span> used to mark up the text to be used for
95
+ editing. There are two ways around this:
96
+
97
+ * Patch getText to a better implementation as described on the
98
+ Scriptaculous Wiki (why is this not in their core release?!)
99
+
100
+ https://madrobby.github.com/scriptaculous/ajax-inplaceeditor/
101
+
102
+ Object.extend(Ajax.InPlaceEditor.prototype, {
103
+ getText: function() {
104
+ return this.element.childNodes[0] ?
105
+ this.element.childNodes[0].nodeValue : '';
106
+ }
107
+ });
108
+
109
+ * Have a custom method to return the unescaped value of the field
110
+ sitting on the server side (again, as for the Textile and Markaby
111
+ examples) - but this means one more controller method and a server
112
+ round-trip each time an editor is shown.
113
+
114
+ Safe In Place Editing patches getText as described above, escapes values in
115
+ the initially created form and its auto-generated helpers, if used, escape
116
+ updated values when rendering them for the in-place view update. You can
117
+ defeat escaping in the safe_in_place_editor_field helper method with a
118
+ special override parameter but this is strongly discouraged.
119
+
120
+ You must include the JavaScript code for the getText patch and supporting
121
+ methods when using the plugin:
122
+
123
+ <%= javascript_include_tag( "safe_in_place_editing/safe_in_place_editing" ) %>
124
+
125
+
126
+ === 2: InPlaceEditing bypasses optimistic locking
127
+
128
+ Optimistic locking is a safety feature in Rails which even the core
129
+ developers seem to forget about sometimes! It associates a version number
130
+ with models. If two users are viewing an 'edit' page for that model and one of
131
+ them submits their form first, then when the other user tries to submit their
132
+ own edits, Rails will detect that the associated version number on their form
133
+ is too old and raise a locking error. Without this, the second user would be
134
+ able to just overwrite the edits made by the first user without anybody
135
+ realising this error had happened.
136
+
137
+ Unfortunately the stock InPlaceEditing plugin is written in such a way that
138
+ locking is bypassed; an in-place editor always succeeds, no matter how out of
139
+ date the view in which the editor resides happens to be.
140
+
141
+ One might take the approach of the anti-forgery request mechanism patch needed
142
+ to get the in-place edit plug-in working with Rails 2, extending the ":with"
143
+ key's value in the options hash with a query string passing the lock_version
144
+ through:
145
+
146
+ if ( object.respond_to?( :lock_version ) )
147
+ in_place_editor_options[ :with ] ||= "Form.serialize(form)"
148
+ in_place_editor_options[ :with ] += " + '&lock_version=#{ object.lock_version }'"
149
+ end
150
+
151
+ The update code on the server side could manually check this against the
152
+ object it just found in the database. Unfortunately the client JavaScript
153
+ code is static, so after a first update the form itself is out of date,
154
+ passing an old lock version through and all subsequent update attempts
155
+ fail until the whole view is reloaded.
156
+
157
+ lock_version = nil
158
+ lock_version = @item.lock_version.to_s if ( @item.respond_to?( :lock_version ) )
159
+
160
+ if ( lock_version != params[ :lock_version ] )
161
+ # Somebody else already edited this item. Do "something"
162
+ # (see later).
163
+ else
164
+ @item.update_attribute( attribute, params[ :value ] )
165
+ end
166
+
167
+ We might attempt to write out JS which assigns global variables unique to
168
+ each form with the initial lock value. An on-complete JS handler could
169
+ then increment the lock version at the client side. This seems ridiculously
170
+ over complicated given the task at hand, requires one to override the
171
+ default on-complete handler, bypass or extend the Rails plug-in (since it
172
+ offers no interface to change the on-complete handler details) and the
173
+ client might still get out of sync with the server's lock versions. Since
174
+ there is little alternative, however, Safe In Place Editing takes this
175
+ heavyweight approach, using extra JS support methods to try and reduce the
176
+ inline code baggage.
177
+
178
+
179
+ === 3: InPlaceEditing's error handling seems to be faulty
180
+
181
+ In theory, returning a 500 error should lead to the onFailure handler running
182
+ in the JS domain, but when used from Rails 2.0.2, just about all properties of
183
+ the 'transport' object used in the default handler function are undefined with
184
+ the standard InPlaceEditing plugin. As a result, no alert box can be shown to the user. The onComplete handler is *always* run, regardless of whether the
185
+ request returns a 2xx or other status code and this leads to numerous problems
186
+ when trying to elegantly handle errors.
187
+
188
+ The JavaScript assistance functions included with SafeInPlaceEditing take
189
+ care of error handling for you. If the on-failure code seems to be having
190
+ trouble then the on-complete code will take over. These functions are also
191
+ used to support optimistic locking as described above.
192
+
193
+
194
+ == Usage examples
195
+
196
+ Any view using Safe In Place Editing requires the Prototype JavaScript library
197
+ to be included. See https://github.com/rails/prototype-rails for details. Most
198
+ people just do this in the "app/assets/javascripts/application.js" file.
199
+
200
+ Either in your main application layout file, add:
201
+
202
+ <%= javascript_include_tag( "safe_in_place_editing" ) %>
203
+
204
+ ...in the document <head> section, or include the script via the asset pipeline
205
+ by editing "app/assets/javascripts/application.js" and adding:
206
+
207
+ //= require safe_in_place_editing
208
+
209
+ Whether you include these resources globally or only for the views that require
210
+ them, don't forget to add both the Prototype JavaScript library files and the
211
+ Safe In Place Editing support code.
212
+
213
+ In your controller, declare the models and model attribute names which are
214
+ to be available for in-place editing. These declarations cause actions to be
215
+ defined in your controller on your behalf; the actions handle the XHR requests
216
+ from the client JavaScript code executing in web browsers when users alter
217
+ attributes of a model via an in-place editor control.
218
+
219
+ safe_in_place_edit_for( :customer, :title )
220
+ safe_in_place_edit_for( :customer, :code )
221
+
222
+ The above sets up a controller so it allows edits to a model called "Customer"
223
+ for attributes "title" and "code".
224
+
225
+ In the view, wherever you want an editor to be available, add a call to the
226
+ "safe_in_place_editor_field" method. For example, in the case of the controller
227
+ for the "Customer" model above, we might produce a 'show' view for a model
228
+ instance stored in "<tt>@customer</tt>" which includes:
229
+
230
+ <strong>Title:</strong>
231
+ <%= safe_in_place_editor_field( @customer, :title ) %>
232
+ <br />
233
+
234
+ <strong>Code:</strong>
235
+ <%= safe_in_place_editor_field( @customer, :code ) %>
236
+ <br />
237
+
238
+ If you're familiar with the API for the InPlaceEditing plugin, you may be
239
+ surprised at the use of "<tt>@customer</tt>" rather than ":customer" in the
240
+ call to "safe_in_place_editor_field". In fact, this call supports _either_
241
+ form - you can pass a symbol "<tt>:foo</tt>", in which case the plugin assumes
242
+ that an instance variable "<tt>@foo</tt>" is available - or you can just pass
243
+ in the variable value directly. Judging by Google searches this quirk of the
244
+ InPlaceEditing API seemed to trip up quite a few people and I saw no reason to
245
+ duplicate that quirk with Safe In Place Editing!
246
+
247
+
248
+ === Boolean values
249
+
250
+ As an added bonus, Safe In Place Editing has special support for boolean values
251
+ in models. If you have a true/false field, an in-place editor will show a small
252
+ pop-up menu including "Yes" and "No" entries.
253
+
254
+ Suppose we have a Task model and the task can be marked as currently active, or
255
+ inactive. To this end it has an attribute "active" which is a boolean property.
256
+ We can create an in-place editor for this by first enabling editing in the
257
+ controller for Tasks:
258
+
259
+ safe_in_place_edit_for( :task, :active )
260
+
261
+ ...then in the view, inserting an in-place editor where we might otherwise have
262
+ just shown the value of the 'active' attribute as a simple piece of text:
263
+
264
+ <strong>Active:</strong>
265
+ <%= safe_in_place_editor_field( @task, :active ) %>
266
+
267
+ It's that simple; the plugin code takes care of the rest. There are numerous
268
+ additional options which can be passed to the various calls; see the API
269
+ documentation for InPlaceEditing for the basics, then check the API
270
+ documentation here for any exceptions or additions.
271
+
272
+
273
+ === Suggested CSS
274
+
275
+ You can style in-place editors however you like; the default Rails scaffold
276
+ styles may be sufficient. If using scaffolding, though, you might like to try
277
+ out the following additional styles as I think they give good results:
278
+
279
+ form.inplaceeditor-form {
280
+ position: absolute;
281
+ background: white;
282
+ border: 2px solid #888;
283
+ text-align: center;
284
+ }
285
+
286
+ form.inplaceeditor-form input[ type="text" ] {
287
+ margin: 5px;
288
+ width: 90%;
289
+ }
290
+
291
+ form.inplaceeditor-form input[ type="submit" ] {
292
+ margin: 5px;
293
+ float: left;
294
+ }
295
+
296
+ form.inplaceeditor-form a {
297
+ margin: 5px;
298
+ float: right;
299
+ }
300
+
301
+ form.inplaceeditor-form select {
302
+ margin: 5px;
303
+ float: left;
304
+ }
305
+
306
+
307
+ == Contacts
308
+
309
+ Ideally please raise issues, suggestions, pull requests etc. via GitHub:
310
+
311
+ * https://github.com/pond/safe_in_place_editing
312
+
313
+ Alternatively free to contact me at "ahodgkin@rowing.org.uk".
314
+
315
+
316
+ = Copyright
317
+
318
+ Copyright (c) 2008-2013 Hipposoft, released under the MIT license.
@@ -0,0 +1,78 @@
1
+ /************************************************************************\
2
+ * File: safe_in_place_editing.js *
3
+ * Hipposoft 2008 *
4
+ * *
5
+ * Purpose: Safe, lockable in-place editing - client-side code. *
6
+ * *
7
+ * History: 24-Jun-2008 (ADH): Created. *
8
+ \************************************************************************/
9
+
10
+ /* Stop "Jack &amp; Jill", written for display purposes into an HTML page,
11
+ * from being edited as exactly that - "Jack &amp; Jill" - if the in-place
12
+ * editor is activated due to Prototype's use of "innerHTML" in its internal
13
+ * "getText" function. See:
14
+ *
15
+ * http://github.com/madrobby/scriptaculous/wikis/ajax-inplaceeditor
16
+ */
17
+
18
+ Object.extend
19
+ (
20
+ Ajax.InPlaceEditor.prototype,
21
+ {
22
+ getText: function()
23
+ {
24
+ return this.element.childNodes[ 0 ] ? this.element.childNodes[ 0 ].nodeValue : '';
25
+ }
26
+ }
27
+ );
28
+
29
+ /* Support the "on success" and "on failure" functions */
30
+
31
+ var safeInPlaceEditorDoneFailureReport = false;
32
+
33
+ /* Custom in-place editor "on failure" function */
34
+
35
+ function safeInPlaceEditorOnFailure( transport )
36
+ {
37
+ if ( transport.responseText )
38
+ {
39
+ safeInPlaceEditorRaiseAlert( transport );
40
+ safeInPlaceEditorDoneFailureReport = true;
41
+ }
42
+ else
43
+ {
44
+ safeInPlaceEditorDoneFailureReport = false;
45
+ }
46
+ }
47
+
48
+ /* Custom in-place editor "on complete" function (call by proxy to set
49
+ * the value of 'lockVar' with the name of the lock variable, if any,
50
+ * held in global (i.e. 'window') context).
51
+ */
52
+
53
+ function safeInPlaceEditorOnComplete( transport, element, lockVar )
54
+ {
55
+ if ( transport.status == 200 )
56
+ {
57
+ if ( lockVar ) window[ lockVar ] += 1;
58
+ }
59
+ else if ( ! safeInPlaceEditorDoneFailureReport )
60
+ {
61
+ safeInPlaceEditorRaiseAlert( transport );
62
+ }
63
+
64
+ safeInPlaceEditorDoneFailureReport = false;
65
+ }
66
+
67
+ /* Helper function - raise an alert describing the given transport object's
68
+ * responseText value.
69
+ */
70
+
71
+ function safeInPlaceEditorRaiseAlert( transport )
72
+ {
73
+ alert
74
+ (
75
+ "Error communicating with the server: " +
76
+ transport.responseText.stripTags()
77
+ );
78
+ }
@@ -0,0 +1,15 @@
1
+ require 'safe_in_place_editing/version'
2
+ require 'safe_in_place_editing/controller_methods'
3
+ require 'safe_in_place_editing/helper_methods'
4
+
5
+ if defined? ActionController
6
+ ActionController::Base.send :include, SafeInPlaceEditing
7
+ ActionController::Base.helper SafeInPlaceEditingHelper
8
+ end
9
+
10
+ module SafeInPlaceEditing
11
+ module Rails
12
+ class Engine < ::Rails::Engine
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,123 @@
1
+ ########################################################################
2
+ # File:: safe_in_place_editing.rb
3
+ # (C):: Hipposoft 2008, 2009
4
+ #
5
+ # Purpose:: Safe, lockable in-place editing - controller support.
6
+ # ----------------------------------------------------------------------
7
+ # 24-Jun-2008 (ADH): Created.
8
+ # 22-Oct-2013 (ADH): Incorporated into 'gemified' sources.
9
+ ########################################################################
10
+
11
+ module SafeInPlaceEditing
12
+
13
+ def self.included( base ) # :nodoc:
14
+ base.extend( ClassMethods )
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ # == Overview
20
+ #
21
+ # API equivalent of in_place_edit_for circa 2009, except:
22
+ #
23
+ # - Runs all user data through "ERB::Util::html_escape" when sending it to
24
+ # the view to avoid associated vulnerabilities with otherwise-unescaped
25
+ # user-supplied data; the current InPlaceEditing plugin does this too,
26
+ # albeit using "CGI::escapeHTML" for some reason.
27
+ #
28
+ # - Supports optimistic locking if a lock_version CGI parameter is
29
+ # supplied, by explicitly checking the version being updated.
30
+ #
31
+ # - Explicitly catches errors and returns them as 500 status codes
32
+ # with a plain text message regardless of Rails environment.
33
+ #
34
+ # - You must include some support JavaScript code and Prototype 1.7.x is
35
+ # assumed - Rails default tends to be 1.6.x - though it _should_ still
36
+ # work with older Prototype library versions. See the example below for
37
+ # details.
38
+ #
39
+ # See +safe_in_place_editor+ and +safe_in_place_editor_field+ for the
40
+ # counterpart helper functions.
41
+ #
42
+ #
43
+ # == Simple example
44
+ #
45
+ # This is adapted from the repository at:
46
+ #
47
+ # * https://github.com/amerine/in_place_editing
48
+ #
49
+ # ...many thanks to those involved.
50
+ #
51
+ # # Controller
52
+ # #
53
+ # class BlogController < ApplicationController
54
+ # safe_in_place_edit_for( :post, :title )
55
+ # end
56
+ #
57
+ # # View
58
+ # #
59
+ # <%= safe_in_place_editor_field( :post, 'title' ) %>
60
+ #
61
+ # # Application layout file, document <head> section
62
+ # #
63
+ # <%= javascript_include_tag( "safe_in_place_editing/safe_in_place_editing" ) %>
64
+ #
65
+ def safe_in_place_edit_for( object, attribute, options = {} )
66
+ define_method( "set_#{ object }_#{ attribute }" ) do
67
+ safe_in_place_edit_backend( object, attribute, options )
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ # Back-end for "safe_in_place_edit_for" - the actual invoked implementation
75
+ # of the dynamically created functions.
76
+ #
77
+ def safe_in_place_edit_backend( object, attribute, options )
78
+ @item = object.to_s.camelize.constantize.find( params[ :id ] )
79
+
80
+ lock_version = nil
81
+ lock_version = @item.lock_version.to_s if ( @item.respond_to?( :lock_version ) )
82
+
83
+ if ( params.include?( :lock_version ) and lock_version != params[ :lock_version ] )
84
+ render( { :status => 500, :text => "Somebody else already edited this #{ object.to_s.humanize.downcase }. Reload the page to obtain the updated version." } )
85
+ return
86
+ else
87
+ begin
88
+
89
+ # Call "touch" to make sure the item is modified even if the user has
90
+ # actually just submitted the form with an unchanged variable. This
91
+ # makes sure that Rails sees the object as 'dirty' and saves it. For
92
+ # objects with lock versions, that means the lock version always
93
+ # increments. The JavaScript code has to assume such an increment and
94
+ # has no clear way to know if it doesn't happen; we could dream up
95
+ # something complex but simpler just to ensure Rails is in step.
96
+ #
97
+ # In the worst possible case, JavaScript and Rails end up out of step
98
+ # with the lock version and the user gets told there's a mismatch. A
99
+ # page reload later and everything is sorted out.
100
+
101
+ success = @item.update_attribute( attribute, params[ :value ] )
102
+ success = @item.touch if ( success && ! lock_version.nil? && @item.lock_version.to_s == lock_version )
103
+
104
+ raise "Unable to save changes to database" unless ( success )
105
+
106
+ rescue => error
107
+ render( { :status => 500, :text => error.message } )
108
+ return
109
+
110
+ end
111
+ end
112
+
113
+ value = @item.send( attribute )
114
+
115
+ if ( ( value.is_a? TrueClass ) || ( value.is_a? FalseClass ) )
116
+ value = value ? 'Yes' : 'No'
117
+ else
118
+ value = ERB::Util::html_escape( value.to_s )
119
+ end
120
+
121
+ render( { :text => value } )
122
+ end
123
+ end
@@ -0,0 +1,287 @@
1
+ ########################################################################
2
+ # File:: safe_in_place_editing_helper.rb
3
+ # (C):: Hipposoft 2008, 2009
4
+ #
5
+ # Purpose:: Safe, lockable in-place editing - helper methods.
6
+ # ----------------------------------------------------------------------
7
+ # 24-Jun-2008 (ADH): Created.
8
+ # 22-Oct-2013 (ADH): Incorporated into 'gemified' sources.
9
+ ########################################################################
10
+
11
+ module SafeInPlaceEditingHelper
12
+
13
+ # == Overview
14
+ #
15
+ # API equivalent of in_place_editor circa 2009, except fixes various bugs
16
+ # (see README.rdoc for the rationale) and:
17
+ #
18
+ # * New option ":lock_var", which is the name of a global variable to be
19
+ # used in the JS domain to track the lock version at the client side. By
20
+ # default set to nil, meaning no optimistic locking support. Options
21
+ # value ":lock_version" MUST be set to the lock version of the object for
22
+ # which the in-place editor is being created in this case. The variable
23
+ # is incremented each time the object is successfully updated through the
24
+ # in-place editor, since the server will have incremented its lock version
25
+ # so the client must keep in step. If someone else edits the item, the
26
+ # client and server lock versions will not match and the update will fail,
27
+ # which is the desired result.
28
+ #
29
+ # * The ":save_text" option is set to "OK" by default, since I detest that
30
+ # nasty lower case "ok" button that's produced by the JS code otherwise.
31
+ #
32
+ # * The ":cancel_text" option is set to "Cancel" by default to match the
33
+ # above change.
34
+ #
35
+ # * New option ":is_boolean" indicating a true/false popup should be offered
36
+ # instead of a text field; if omitted, a text field is assumed. If present
37
+ # and 'true', additional optional value ":first_value" says whether or not
38
+ # the pop-up menu should start with True/Yes (if ":first_value"'s value is
39
+ # itself true), or False/No (if ":first_value"'s value is itself false, or
40
+ # if the option is omitted).
41
+ #
42
+ # Custom on-failure and on-complete functions are used. To try and reduce
43
+ # the code bulk for each instance of the editor, hard-coded JS function names
44
+ # are used with the support code placed in 'safe_in_place_editing.js'. See
45
+ # there for a reference implementation if intending to write customised
46
+ # equivalents. To override the default names of these functions for any
47
+ # reason, give the names as strings in options properties :on_complete and
48
+ # :on_failure, then make sure appropriate JS functions are actually defined.
49
+ #
50
+ #
51
+ # == Original In Place Editor documentation
52
+ #
53
+ # This is copied from the repository at:
54
+ #
55
+ # * https://github.com/amerine/in_place_editing
56
+ #
57
+ # ...many thanks to those involved.
58
+ #
59
+ # Makes an HTML element specified by the DOM ID +field_id+ become an in-place
60
+ # editor of a property.
61
+ #
62
+ # A form is automatically created and displayed when the user clicks the element,
63
+ # something like this:
64
+ #
65
+ # <form id="myElement-in-place-edit-form" target="specified url">
66
+ # <input name="value" text="The content of myElement"/>
67
+ # <input type="submit" value="ok"/>
68
+ # <a onclick="javascript to cancel the editing">cancel</a>
69
+ # </form>
70
+ #
71
+ # The form is serialized and sent to the server using an AJAX call, the action on
72
+ # the server should process the value and return the updated value in the body of
73
+ # the reponse. The element will automatically be updated with the changed value
74
+ # (as returned from the server).
75
+ #
76
+ # options with (Must be a function) means that you must pass a string like:
77
+ #
78
+ # function(transport, element) {what_to_do()}
79
+ #
80
+ # Required +options+ are:
81
+ # <tt>:url</tt>:: Specifies the url where the updated value should
82
+ # be sent after the user presses "ok".
83
+ #
84
+ # Addtional +options+ are:
85
+ # <tt>:rows</tt>:: Number of rows (more than 1 will use a TEXTAREA)
86
+ # <tt>:cols</tt>:: Number of characters the text input should span (works for both INPUT and TEXTAREA)
87
+ # <tt>:size</tt>:: Synonym for :cols when using a single line text input.
88
+ # <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
89
+ # <tt>:save_text</tt>:: The text on the save link. (default: "ok")
90
+ # <tt>:loading_text</tt>:: The text to display while the data is being loaded from the server (default: "Loading...")
91
+ # <tt>:saving_text</tt>:: The text to display when submitting to the server (default: "Saving...")
92
+ # <tt>:external_control</tt>:: The id of an external control used to enter edit mode.
93
+ # <tt>:load_text_url</tt>:: URL where initial value of editor (content) is retrieved.
94
+ # <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
95
+ # <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
96
+ # in the AJAX call, +form+ is an implicit parameter
97
+ # <tt>:script</tt>:: Instructs the in-place editor to evaluate the remote JavaScript response (default: false)
98
+ # <tt>:click_to_edit_text</tt>:: The text shown during mouseover the editable text (default: "Click to edit")
99
+ # <tt>:on_complete</tt>:: (Must be a function)Code run if update successful with server. Also if user cancels the form (see https://prototype.lighthouseapp.com/projects/8887/tickets/243).
100
+ # <tt>:on_failure</tt>:: (Must be a function)Code run if update failed with server
101
+ #
102
+ def safe_in_place_editor( field_id, options = {} )
103
+
104
+ # Set up some default values
105
+
106
+ options[ :with ] ||= "Form.serialize(form).replace(/\\+/g,'%20')"
107
+
108
+ if protect_against_forgery?
109
+ options[ :with ] += " + '&authenticity_token=' + encodeURIComponent('#{ form_authenticity_token }')"
110
+ end
111
+
112
+ options[ :on_complete ] ||= 'safeInPlaceEditorOnComplete'
113
+ options[ :on_failure ] ||= 'safeInPlaceEditorOnFailure'
114
+ options[ :save_text ] ||= 'OK'
115
+ options[ :cancel_text ] ||= 'Cancel'
116
+
117
+ # Preliminary script data
118
+
119
+ if ( options.include?( :lock_var ) )
120
+ function = "window['#{ options[ :lock_var ] }']=#{ options[ :lock_version ] };"
121
+ else
122
+ function = ''
123
+ end
124
+
125
+ function_name = options[ :is_boolean ] ? 'InPlaceCollectionEditor' : 'InPlaceEditor'
126
+
127
+ function << "new Ajax.#{ function_name }("
128
+ function << "'#{ field_id }', "
129
+ function << "'#{ url_for( options[ :url ] ) }'"
130
+
131
+ # Map Rails in-place editor options to JS in-place editor options - see:
132
+ #
133
+ # http://github.com/madrobby/scriptaculous/wikis/ajax-inplaceeditor
134
+
135
+ js_options = {}
136
+
137
+ js_options[ 'rows' ] = options[ :rows ] if options[ :rows ]
138
+ js_options[ 'cols' ] = options[ :cols ] if options[ :cols ]
139
+ js_options[ 'size' ] = options[ :size ] if options[ :size ]
140
+ js_options[ 'ajaxOptions' ] = options[ :options ] if options[ :options ]
141
+ js_options[ 'htmlResponse' ] = ! options[ :script ] if options[ :script ]
142
+
143
+ js_options[ 'cancelText' ] = %('#{ options[ :cancel_text ] }') if options[ :cancel_text ]
144
+ js_options[ 'okText' ] = %('#{ options[ :save_text ] }') if options[ :save_text ]
145
+ js_options[ 'loadingText' ] = %('#{ options[ :loading_text ] }') if options[ :loading_text ]
146
+ js_options[ 'savingText' ] = %('#{ options[ :saving_text ] }') if options[ :saving_text ]
147
+ js_options[ 'clickToEditText' ] = %('#{ options[ :click_to_edit_text ] }') if options[ :click_to_edit_text ]
148
+ js_options[ 'textBetweenControls' ] = %('#{ options[ :text_between_controls ] }') if options[ :text_between_controls ]
149
+
150
+ js_options[ 'externalControl' ] = "'#{ options[ :external_control ] }'" if options[ :external_control ]
151
+ js_options[ 'loadTextURL' ] = "'#{ url_for( options[ :load_text_url ] ) }'" if options[ :load_text_url ]
152
+
153
+ js_options[ 'callback' ] = "function(form) { return #{ options[ :with ] }; }" if options[ :with ]
154
+
155
+ if options[ :is_boolean ]
156
+ if options[ :start_value ]
157
+ js_options[ 'collection' ] = "[['true','Yes'],['false','No']]"
158
+ else
159
+ js_options[ 'collection' ] = "[['false','No'],['true','Yes']]"
160
+ end
161
+ end
162
+
163
+ # Set up the custom on-failure and on-complete handlers
164
+
165
+ js_options[ 'onFailure' ] = "#{ options[ :on_failure ] }"
166
+
167
+ if ( options.include?( :lock_var ) )
168
+ js_options['onComplete'] = "function(transport, element) {#{ options[ :on_complete ] }(transport,element,'#{ options[ :lock_var ] }');}"
169
+ else
170
+ js_options['onComplete'] = "#{ options[ :on_complete ] }"
171
+ end
172
+
173
+ # Assemble the content
174
+
175
+ function << ( ', ' + options_for_javascript( js_options ) ) unless js_options.empty?
176
+ function << ')'
177
+
178
+ return javascript_tag( function )
179
+ end
180
+
181
+ # Close API equivalent of in_place_editor_field, except fixes various bugs
182
+ # (see the README for rationale). Allows either an object name in the first
183
+ # parameter (e.g. ":foo", in which case instance variable "@foo" must point
184
+ # to the object instance of interest) or an object instance (to save messing
185
+ # around with magic instance variables, but obtains the object name from
186
+ # "class.name.underscore", so may not be appropriate for unusual object
187
+ # classes). Anyway, clearer error reporting and the ability to pass in an
188
+ # object reference directly may help avoid a common error experienced with
189
+ # the InPlaceEditing plug-in code, as described here at the time of writing:
190
+ #
191
+ # http://oldwiki.rubyonrails.org/rails/pages/InPlaceEditing
192
+ #
193
+ # Includes the following options:
194
+ #
195
+ # * :lock_var is the name of the variable used for optimistic locking, by
196
+ # default set to a row-unique value. The assumption is that this same
197
+ # variable gets used throughout the row so that multiple edits on that
198
+ # row all cause the same variable to be incremented. If your back-end
199
+ # update function has side effects that might invalidate the value shown
200
+ # in another column on that row - which would be pretty strange! - you'd
201
+ # need to override the lock variable name with something that's unique to
202
+ # both the row and the column. When using a lock variable, additional
203
+ # option ":lock_version" is always set internally to the lock version of
204
+ # the object for which the field is being built and cannot be overridden.
205
+ #
206
+ # * :with is extended to include a "lock_version" parameter in the query
207
+ # string so that the client side's idea of the current object's lock
208
+ # version may be communicated to the server's attribute update action.
209
+ # This is done internally; there is no need to set the option yourself.
210
+ #
211
+ # The editor options also support "is_boolean", which overrides the default
212
+ # setting of whether or not the column value is considered to be a string
213
+ # or a boolean quantity. This is provided just-in-case, with no current
214
+ # known cases where the automatic detection isn't sufficient.
215
+ #
216
+ # The Prototype library getText function must be patched as described in
217
+ # the README rationale; "application.js" is a good place to do this.
218
+ #
219
+ # Note an optional fifth parameter which if 'true' will prevent HTML
220
+ # escaping of the value for values which are really meant to contain HTML
221
+ # code. Be very, very careful with this.
222
+ #
223
+ def safe_in_place_editor_field( object, method, tag_options = {}, editor_options = {}, no_escape = false )
224
+
225
+ # Allow a symbol or object instance to be passed. Since the symbol use
226
+ # case involves accessing a 'magic' related instance variable name and
227
+ # since there are lots of examples via Google of this confusing people,
228
+ # raise a helpful error message if the relevant variable is missing.
229
+
230
+ if ( object.instance_of?( Symbol ) )
231
+ object_name = object
232
+ var_name = "@#{ object_name }"
233
+ if ( instance_variable_defined?( var_name ) )
234
+ object = instance_variable_get( var_name )
235
+ else
236
+ raise( 'If passing \':foo\' to in_place_editor_field, \'@foo\' must refer to the object for which the field is being built' )
237
+ end
238
+ else
239
+ object_name = object.class.name.underscore
240
+ end
241
+
242
+ # Pass the lock version in for optimistic locking support, should the
243
+ # object support it. The update callback function must manually compare
244
+ # the params[ :lock_version ] value against the lock_version.to_s()
245
+ # value of the object that's being updated.
246
+
247
+ if ( object.respond_to?( :lock_version ) )
248
+ var = "#{ object_name }_#{ object.id }_safeInPlaceEditorLockVersion"
249
+
250
+ editor_options[ :lock_version ] = object.lock_version.to_s
251
+ editor_options[ :lock_var ] ||= var
252
+ editor_options[ :with ] ||= "Form.serialize(form).replace(/\\+/g,'%20')"
253
+ editor_options[ :with ] += " + '&lock_version=' + #{ var }"
254
+ end
255
+
256
+ # Escape the value unless told not to and construct the complete in-place
257
+ # editor assembly. Check for boolean values too, allowing caller-override.
258
+
259
+ column_value = object.send( method )
260
+
261
+ is_boolean = ( editor_options[ :is_boolean ] || ( column_value.is_a? TrueClass ) || ( column_value.is_a? FalseClass ) )
262
+
263
+ if ( is_boolean )
264
+ editor_options[ :start_value ] = !! column_value
265
+ column_value = column_value ? 'Yes' : 'No'
266
+ else
267
+ column_value = ERB::Util::html_escape( column_value ) unless ( no_escape )
268
+ end
269
+
270
+ tag_options = {
271
+ :id => "#{ object_name }_#{ method }_#{ object.id }_in_place_editor",
272
+ :class => "in_place_editor_field"
273
+ }.merge!( tag_options )
274
+
275
+ editor_options[ :url ] ||= url_for( {
276
+ :action => "set_#{ object_name }_#{ method }",
277
+ :id => object.id
278
+ } )
279
+
280
+ # Update the boolean value flag, unless the caller had already set one.
281
+
282
+ editor_options[ :is_boolean ] = is_boolean unless editor_options.has_key?( :is_boolean )
283
+
284
+ return content_tag( :span, column_value.html_safe, tag_options ) +
285
+ safe_in_place_editor( tag_options[ :id ], editor_options )
286
+ end
287
+ end
@@ -0,0 +1,3 @@
1
+ module SafeInPlaceEditing
2
+ VERSION = "2.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: safe_in_place_editing
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Hodgkinson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-11-29 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: railties
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: "3.2"
22
+ type: :runtime
23
+ version_requirements: *id001
24
+ description: Safe In Place Editing Rails extension, providing flexible HTML safe in-place editing with string and boolean type support
25
+ email:
26
+ - ahodgkin@rowing.org.uk
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/safe_in_place_editing/controller_methods.rb
35
+ - lib/safe_in_place_editing/helper_methods.rb
36
+ - lib/safe_in_place_editing/version.rb
37
+ - lib/safe_in_place_editing.rb
38
+ - app/assets/safe_in_place_editing/safe_in_place_editing.js
39
+ - MIT-LICENSE
40
+ - README.rdoc
41
+ homepage: https://github.com/pond/safe_in_place_editing
42
+ licenses: []
43
+
44
+ metadata: {}
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - &id002
54
+ - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - *id002
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 2.1.11
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Safe In Place Editing Rails extension
67
+ test_files: []
68
+