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+.