Module SafeInPlaceEditingHelper
In: vendor/plugins/safe_in_place_editing/lib/safe_in_place_editing_helper.rb

File: safe_in_place_editing_helper.rb #

         Hipposoft 2008                                              #
                                                                     #

Purpose: Safe, lockable in-place editing - helper methods. #

                                                                     #

History: 24-Jun-2008 (ADH): Created. #

Methods

Public Instance methods

Exact API equivalent of in_place_editor, except fixes various bugs (see README for the rationale) and:

  • Includes an equivalent of the Rails 2 CSRF plug-in patch.
  • New option ":lock_var", which is the name of a global variable to be used in the JS domain to track the lock version at the client side. By default set to nil, meaning no optimistic locking support. Options value ":lock_version" MUST be set to the lock version of the object for which the in-place editor is being created in this case. The variable is incremented each time the object is successfully updated through the in-place editor, since the server will have incremented its lock version so the client must keep in step. If someone else edits the item, the client and server lock versions will not match and the update will fail, which is the desired result.
  • The ":save_text" option is set to "OK" by default, since I detest that nasty lower case "ok" button that‘s produced by the JS code otherwise.
  • The ":cancel_text" option is set to "Cancel" by default to match the above change.

Custom on-failure and on-complete functions are used. To try and reduce the code bulk for each instance of the editor, hard-coded JS function names are used with the support code placed in ‘safe_in_place_editing.js’. See there for a reference implementation if intending to write customised equivalents. To override the default names of these functions for any reason, give the names as strings in options properties :on_complete and :on_failure, then make sure appropriate JS functions are actually defined.

[Source]

     # File vendor/plugins/safe_in_place_editing/lib/safe_in_place_editing_helper.rb, line 42
 42:   def safe_in_place_editor( field_id, options = {} )
 43: 
 44:     # Set up some default values
 45: 
 46:     if protect_against_forgery?
 47:       options[ :with ] ||= "Form.serialize(form)"
 48:       options[ :with ] += " + '&authenticity_token=' + encodeURIComponent('#{ form_authenticity_token }')"
 49:     end
 50: 
 51:     options[ :on_complete ] ||= 'safeInPlaceEditorOnComplete'
 52:     options[ :on_failure  ] ||= 'safeInPlaceEditorOnFailure'
 53:     options[ :save_text   ] ||= 'OK'
 54:     options[ :cancel_text ] ||= 'Cancel'
 55: 
 56:     # Preliminary script data
 57: 
 58:     if ( options.include?( :lock_var ) )
 59:       function = "window['#{ options[ :lock_var ] }']=#{ options[ :lock_version ] };"
 60:     else
 61:       function = ''
 62:     end
 63: 
 64:     function_name = options[ :is_boolean ] ? 'InPlaceCollectionEditor' : 'InPlaceEditor'
 65: 
 66:     function << "new Ajax.#{ function_name }("
 67:     function << "'#{ field_id }', "
 68:     function << "'#{ url_for( options[ :url ] ) }'"
 69: 
 70:     # Map Rails in-place editor options to JS in-place editor options - see:
 71:     #
 72:     #   http://github.com/madrobby/scriptaculous/wikis/ajax-inplaceeditor
 73: 
 74:     js_options = {}
 75: 
 76:     js_options[ 'rows'                ] =   options[ :rows    ] if options[ :rows    ]
 77:     js_options[ 'cols'                ] =   options[ :cols    ] if options[ :cols    ]
 78:     js_options[ 'size'                ] =   options[ :size    ] if options[ :size    ]
 79:     js_options[ 'ajaxOptions'         ] =   options[ :options ] if options[ :options ]
 80:     js_options[ 'htmlResponse'        ] = ! options[ :script  ] if options[ :script  ]
 81: 
 82:     js_options[ 'cancelText'          ] = %('#{ options[ :cancel_text           ] }') if options[ :cancel_text           ]
 83:     js_options[ 'okText'              ] = %('#{ options[ :save_text             ] }') if options[ :save_text             ]
 84:     js_options[ 'loadingText'         ] = %('#{ options[ :loading_text          ] }') if options[ :loading_text          ]
 85:     js_options[ 'savingText'          ] = %('#{ options[ :saving_text           ] }') if options[ :saving_text           ]
 86:     js_options[ 'clickToEditText'     ] = %('#{ options[ :click_to_edit_text    ] }') if options[ :click_to_edit_text    ]
 87:     js_options[ 'textBetweenControls' ] = %('#{ options[ :text_between_controls ] }') if options[ :text_between_controls ]
 88: 
 89:     js_options[ 'externalControl'     ] = "'#{          options[ :external_control ]   }'" if options[ :external_control ]
 90:     js_options[ 'loadTextURL'         ] = "'#{ url_for( options[ :load_text_url    ] ) }'" if options[ :load_text_url    ]
 91: 
 92:     js_options[ 'callback'            ] = "function(form) { return #{ options[ :with ] }; }" if options[ :with       ]
 93:     js_options[ 'collection'          ] = %([['false','No'],['true','Yes']])                 if options[ :is_boolean ]
 94: 
 95:     # Set up the custom on-failure and on-complete handlers
 96: 
 97:     js_options[ 'onFailure' ] = "#{ options[ :on_failure ] }"
 98: 
 99:     if ( options.include?( :lock_var ) )
100:       js_options['onComplete'] = "function(transport, element) {#{ options[ :on_complete ] }(transport,element,'#{ options[ :lock_var ] }');}"
101:     else
102:       js_options['onComplete'] = "#{ options[ :on_complete ] }"
103:     end
104: 
105:     # Assemble the content
106: 
107:     function << ( ', ' + options_for_javascript( js_options ) ) unless js_options.empty?
108:     function << ')'
109: 
110:     return javascript_tag( function )
111:   end

Exact API equivalent of in_place_editor_field, except fixes various bugs (see the README for rational) and allows either an object name in the first parameter (e.g. ":foo", in which case instance variable "@foo" must point to the object instance of interest) or an object instance (to save messing around with magic instance variables, but obtains the object name from "class.class_name.underscore", so may not be appropriate for unusual object classes). Anyway, clearer error reporting and the ability to pass in an object reference directly may help avoid a common error experienced with the InPlaceEditing plug-in code, as described here at the time of writing:

  http://wiki.rubyonrails.org/rails/pages/InPlaceEditing

Includes the following options:

  • :lock_var is the name of the variable used for optimistic locking, by default set to a row-unique value. The assumption is that this same variable gets used throughout the row so that multiple edits on that row all cause the same variable to be incremented. If your back-end update function has side effects that might invalidate the value shown in another column on that row - which would be pretty strange! - you‘d need to override the lock variable name with something that‘s unique to both the row and the column. When using a lock variable, additional option ":lock_version" is always set internally to the lock version of the object for which the field is being built and cannot be overridden.
  • :with is extended to include a "lock_version" parameter in the query string so that the client side‘s idea of the current object‘s lock version may be communicated to the server‘s attribute update action. This is done internally; there is no need to set the option yourself.

The Prototype library getText function must be patched as described in the README rationale; "application.js" is a good place to do this.

Note an optional fifth parameter which if ‘true’ will prevent HTML escaping of the value for values which are really meant to contain HTML code. Be very, very careful with this.

[Source]

     # File vendor/plugins/safe_in_place_editing/lib/safe_in_place_editing_helper.rb, line 150
150:   def safe_in_place_editor_field( object, method, tag_options = {}, editor_options = {}, no_escape = false )
151: 
152:     # Allow a symbol or object instance to be passed. Since the symbol use
153:     # case involves accessing a 'magic' related instance variable name and
154:     # since there are lots of examples via Google of this confusing people,
155:     # raise a helpful error message if the relevant variable is missing.
156: 
157:     if ( object.instance_of?( Symbol ) )
158:       object_name = object
159:       var_name = "@#{ object_name }"
160:       if ( instance_variable_defined?( var_name ) )
161:         object = instance_variable_get( var_name )
162:       else
163:         raise( 'If passing \':foo\' to in_place_editor_field, \'@foo\' must refer to the object for which the field is being built' )
164:       end
165:     else
166:       object_name = object.class.class_name.underscore
167:     end
168: 
169:     # Pass the lock version in for optimistic locking support, should the
170:     # object support it. The update callback function must manually compare
171:     # the params[ :lock_version ] value against the lock_version.to_s()
172:     # value of the object that's being updated.
173: 
174:     if ( object.respond_to?( :lock_version ) )
175:       var = "#{ object_name }_#{ object.id }_safeInPlaceEditorLockVersion"
176: 
177:       editor_options[ :lock_version ]   = object.lock_version.to_s
178:       editor_options[ :lock_var     ] ||= var
179:       editor_options[ :with         ] ||= "Form.serialize(form)"
180:       editor_options[ :with         ]  += " + '&lock_version=' + #{ var }"
181:     end
182: 
183:     # Escape the value unless told not to and construct the complete in-place
184:     # editor assembly. Check for boolean values too, allowing caller-override.
185: 
186:     column_value = object.send( method )
187: 
188:     is_boolean = ( editor_options[ :is_boolean ] || ( column_value.is_a? TrueClass ) || ( column_value.is_a? FalseClass ) )
189:     
190:     if ( is_boolean )
191:       column_value = column_value ? 'Yes' : 'No'
192:     else
193:       column_value = ERB::Util::html_escape( column_value ) unless ( no_escape )
194:     end
195: 
196:     tag_options = {
197:       :tag   => "span",
198:       :id    => "#{object_name}_#{method}_#{object.id}_in_place_editor",
199:       :class => "in_place_editor_field"
200:     }.merge!( tag_options )
201: 
202:     editor_options[ :url ] ||= url_for( {
203:       :action => "set_#{object_name}_#{method}",
204:       :id     => object.id 
205:     } )
206: 
207:     # Update the boolean value flag, unless the caller had already set one.
208: 
209:     editor_options[ :is_boolean ] = is_boolean unless editor_options.has_key?( :is_boolean )
210: 
211:     return content_tag( :span, column_value, tag_options ) +
212:            safe_in_place_editor( tag_options[ :id ], editor_options )
213:   end

[Validate]