[Rails] in_place_collection_editor
Ruairi Mc Comb
ruairi at thecomb.net
Sat Mar 25 17:31:13 GMT 2006
Hi,
I'm trying to write a helper for Scriptaculous' InPlaceCollectionEditor
component. I've already submitted a patch
(http://dev.rubyonrails.org/ticket/4302). This was a drunk patch; it
needs a bit of work (Don't drink & code!). So far I've gotten it to work
correctly with normal collections, but I want to use it for belongs_to
relations as well.
I want to build a test case for the in_place_collection_editor field for
which I need to know how to do the following (in rails core trunk):
How do I simulate a controller?
How do I simulate an instance variable on that controller?
How do I simulate an Array of ActiveRecord instances? Can this array be
loaded from fixtures?
I basically want to do the following:
The model:
class Post < ActiveRecord::Base
belongs_to :section
end
class Section < ActiveRecord::Base
has_many :posts
end
There would be a "post" instance variable set on the controller.
I've tried setting this up as I've seen in other tests (using structs)
as such:
def setup
@sections = [
Section.new(:id => 1, :name => 'News'),
Section.new(:id => 2, :name => 'Gossip'),
Section.new(:id => 3, :name => 'Slander')
]
@post = Post.new(:id => '1', :title => 'Foo', :section => @sections[0])
@controller = Class.new do
def url_for(options, *parameters_for_method_reference)
url = "http://www.example.com/"
url << options[:action].to_s if options and options[:action]
url
end
end
@controller = @controller.new
end
But I'm not able to use this setup as ActionView::Helpers::InstanceTag
uses a method of CGI to get to the instance variable set on the controller:
def object
@object || @template_object.instance_variable_get("@#{@object_name}")
end
Also the structs don't seem to behave as I expected them to (they don't
seem to respond to .send, nor am I able to access properties), weird
thing is that ActiveRecord objects do behave.
I'd like to be able to develop this using tests. When I'm developing
this as a plugin it's a PITA to have to restart the server for the
changes to reload.
Anybody interested in the code (in plugin form) can check it out from:
http://ruairimccomb.com/svn/in_place_collection_editor/
-------------- next part --------------
Index: actionpack/test/template/java_script_macros_helper_test.rb
===================================================================
--- actionpack/test/template/java_script_macros_helper_test.rb (revision 3940)
+++ actionpack/test/template/java_script_macros_helper_test.rb (working copy)
@@ -9,8 +9,20 @@
include ActionView::Helpers::TextHelper
include ActionView::Helpers::FormHelper
include ActionView::Helpers::CaptureHelper
-
+
+ silence_warnings do
+ Section = Struct.new(:id, :name)
+ Post = Struct.new(:id, :title, :section)
+ end
+
def setup
+ @sections = [
+ Section.new(:id => 1, :name => 'News'),
+ Section.new(:id => 2, :name => 'Gossip'),
+ Section.new(:id => 3, :name => 'Slander')
+ ]
+ @post = Post.new(:id => '1', :title => 'Foo', :section => @sections[0])
+
@controller = Class.new do
def url_for(options, *parameters_for_method_reference)
url = "http://www.example.com/"
@@ -91,4 +103,30 @@
:load_text_url => { :action => "action_to_get_value" })
end
+ def test_in_place_collection_editor_with_simple_array
+ assert_match "Ajax.InPlaceCollectionEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {collection:['1','2','3']})",
+ in_place_collection_editor('id-goes-here',
+ :url => { :action => "action_to_set_value" },
+ :collection => [1,2,3]
+ )
+ end
+
+ def test_in_place_collection_editor_with_multi_array
+ assert_match "Ajax.InPlaceCollectionEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {collection:[['1','one'],['2','two'],['3','three']]})",
+ in_place_collection_editor('id-goes-here',
+ :url => { :action => "action_to_set_value" },
+ :collection => [[1,'one'],[2,'two'],[3,'three']]
+ )
+ end
+
+ def test_in_place_collection_editor_field
+ assert_match "<span class=\"in_place_editor_field\" id=\"post_section_in_place_editor\"></span><script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceCollectionEditor('post_section_in_place_editor', 'http://www.example.com/', {collection:['1','2','3']})\n//]]>\n</script>",
+ in_place_collection_editor_field('post', 'section', @sections)
+ end
+
+ def test_in_place_association_editor_field
+ assert_match "<span class=\"in_place_editor_field\" id=\"post_section_in_place_editor\"></span><script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceCollectionEditor('post_section_in_place_editor', 'http://www.example.com/', {collection:[['1','News'],['2','Gossip'],['3','Slander']]})\n//]]>\n</script>",
+ in_place_association_editor_field('post', 'section', @sections, 'id', 'name')
+ end
+
end
Index: actionpack/lib/action_view/helpers/java_script_macros_helper.rb
===================================================================
--- actionpack/lib/action_view/helpers/java_script_macros_helper.rb (revision 3940)
+++ actionpack/lib/action_view/helpers/java_script_macros_helper.rb (working copy)
@@ -72,6 +72,86 @@
tag.to_content_tag(tag_options.delete(:tag), tag_options) +
in_place_editor(tag_options[:id], in_place_editor_options)
end
+
+ # Makes an HTML element specified by the DOM ID +field_id+ become an in-place
+ # editor of a property using a selection list populated by the .
+ #
+ # A form is automatically created and displayed when the user clicks the element,
+ # something like this:
+ # <form id="myElement-in-place-edit-form" target="specified url">
+ # <select>
+ # <option value="1">One</option>
+ # </select>
+ # <input type="submit" value="ok"/>
+ # <a onclick="javascript to cancel the editing">cancel</a>
+ # </form>
+ #
+ # The form is serialized and sent to the server using an AJAX call, the action on
+ # the server should process the value and return the updated value in the body of
+ # the reponse. The element will automatically be updated with the changed value
+ # (as returned from the server).
+ #
+ # Required +options+ are:
+ # <tt>:url</tt>:: Specifies the url where the updated value should
+ # be sent after the user presses "ok".
+ # <tt>:collection</tt>: The collection to build the select from. This can be either
+ # simple array, or a multilevel array.
+ #
+ #
+ # Addtional +options+ are:
+ # <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
+ # <tt>:save_text</tt>:: The text on the save link. (default: "ok")
+ # <tt>:loading_text</tt>:: The text to display when submitting to the server (default: "Saving...")
+ # <tt>:external_control</tt>:: The id of an external control used to enter edit mode.
+ # <tt>:load_text_url</tt>:: URL where initial value of editor (content) is retrieved.
+ # <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
+ # <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
+ # in the AJAX call, +form+ is an implicit parameter
+ def in_place_collection_editor(field_id, options = {})
+ function = "new Ajax.InPlaceCollectionEditor("
+ function << "'#{field_id}', "
+ function << "'#{url_for(options[:url])}'"
+
+ js_options = {}
+ js_options['collection'] = "[#{options[:collection].collect{|i| array_or_string_for_javascript(i)}.join(',')}]" if options[:collection]
+ js_options['value'] = %('#{options[:value]}') if options[:value]
+ js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text]
+ js_options['okText'] = %('#{options[:save_text]}') if options[:save_text]
+ js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text]
+ js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control]
+ js_options['loadTextURL'] = "'#{url_for(options[:load_text_url])}'" if options[:load_text_url]
+ js_options['ajaxOptions'] = options[:options] if options[:options]
+ js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with]
+ function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
+
+ function << ')'
+
+ javascript_tag(function)
+ end
+
+ # Renders the value of the specified object and method with in-place editing capabilities.
+ #
+ def in_place_collection_editor_field(object, method, collection, tag_options = {}, in_place_collection_editor_options = {})
+ tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
+ tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
+ in_place_collection_editor_options[:collection] = collection # .collect {|c| [c[value_method],c[text_method]]}
+ in_place_collection_editor_options[:value] = tag.object.send(method)
+ in_place_collection_editor_options[:url] = in_place_collection_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
+ tag.to_content_tag(tag_options.delete(:tag), tag_options) +
+ in_place_collection_editor(tag_options[:id], in_place_collection_editor_options)
+ end
+
+ # Renders the value of the specified objects association with in-place editing capabilities.
+ #
+ def in_place_association_editor_field(object, method, collection, value_method, text_method, tag_options = {}, in_place_collection_editor_options = {})
+ tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
+ tag_options = {:tag => "span", :id => "#{object}_#{method}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
+ in_place_collection_editor_options[:collection] = collection.collect{|e| [e[value_method],e[text_method]]}
+ in_place_collection_editor_options[:value] = tag.object.send(method).send(text_method)
+ in_place_collection_editor_options[:url] = in_place_collection_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}" })
+ content_tag(tag_options.delete(:tag), tag.object.send(method).send(text_method), tag_options) +
+ in_place_collection_editor(tag_options[:id], in_place_collection_editor_options)
+ end
# Adds AJAX autocomplete functionality to the text input field with the
# DOM ID specified by +field_id+.
More information about the Rails
mailing list