safe_in_place_editing 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+