Rails / View-level
From WhyNotWiki
[edit] Views: Miscellaneous
[edit] Having an initially-hidden "create" form that displays immediately when needed (the Inline Create Form)
[When Ajax doesn't work (category)] (An example of when you sometimes want to optimize your user interface layer by doing things inline instead of Ajax so that the user doesn't experience latency...) [When to use components (category)]/[When components are useful (category)]
Craig Ambrose. CRUD vs The Inline Create Form (http://blog.craigambrose.com/past/2006/8/26/crud-vs-the-inline-create-form/).
So, we both know the solution. The new comment form has to be written into the article show template. This is such a common technique that I’m sure I can get away with referring to it as a pattern, and putting it in capital letters. Behold the Inline Create Form. Ok, so it’s not all that exciting, but we’re all doing it, and in my opinion the implementation introduces some nasty smells that I’d like to try and minimise.[edit] What About Controller Code to Initialise the Form?
Realistically, we need some code to initialise the form when it is first called. In this simple example, it would probably look like this (current_user is presumably a method on your application controller).
@comment = Comment.new @comment.author = current_userNow this is fairly minimal. We can stick it inside Articles.show, and the world isn’t going to end. If we end up using the form as a stand-alone page, we can stick it in the comments new action as well. However, I certainly have real word example where the code is more complex than this, and even if it is only a couple of lines of code, frankly this is wrong. Putting code to initialise the comment form, inside the article show action, rather than inside the comment new action, and for a reason only based on speeding up the load time of a user interaction is most definitely a bad smell.
The rails API docs suggest removing view/controller duplication using partials and filters. It’s true, we could move this controller code into a before_filter, and apply that filter to the article show action. However, that does not improve the design problem even a tiny bit. The only difference is that it obscures it. In agile development, we call this a deodorant. It doesn’t fix a bad smell in the code, it just hides it.
[edit] Rails Components Solve This Problem
The force is more powerful on the dark side. Have a look at how show.rhtml would be written if we used components.
<%= link_to_function "Add a Comment", "Element.hide('add_comment_link'); Element.show('new_comment_form')", :id => 'add_comment_link' %> <div id="new_comment_form" style="display: none"> <%= render_component :controller => 'comments', :action => 'new' %> </div>A component is an inline render to a totally different rails action, and the controller code for that action gets called too. Hey, that’s exactly what we’re actually doing here! Anything else is just an optimisation. With the above code, the form initialisation code stays where it belongs (in the comments new action). In fact, this code would allow the comments controller to behave in ‘full page mode’ as well, providing it knew to render it’s layout if it isn’t called as a component, but to not render it if it is (and yes, this is easy to do). That gives us a non-Javascript fallback, which might be important for some people. For myself, I just like the elegance of the properly decoupled code.
[edit] Why Components are ‘Bad’
DHH doesn’t like components because he wants to build applications up from very cleverly designed building blocks, rather than sticking vertical slices together (my words, but I think that’s what he means). He has a point, but that isn’t really what we’re trying to do here. Or, if it is, the decision was made as soon as we decided that we wanted an inline form. If we really wanted to de-couple our controllers, we shouldn’t have inlined the form at all. If we decide that it’s a nicer experience for the user if we do inline it (and it is, which is why most apps do it), then we have already introduces the problem, and we need a solution. Of the examples I’ve presented so far, the component approach yields the nicest code.
However, components have speed issues. [...]
[edit] How do I link to a popup window?
<%= link_to( "Give us some feedback", {:controller => 'popup', :action => 'feedback'}, { :popup => ['new_window', 'height=650,width=450,scrollbars=yes'], :target => '_blank' } ) %>
As you can see, you use the :popup option of link_to. That generates a standard Javascript popup call. You can pass all the options that you'd normally use for such a thing, like hight and width.
I usually like to specify a target, too, so that just in case they have Javascript turned off, it will at least open in a new window.
Make sure to create a new, very simple layout for use by your popup windows -- because you probably don't want everything that your normal layouts provide.
[edit] How do I make an image into a link?
Answer: Sorta like this...
<%= link_to image_tag('next.gif'), :action => :next %>
<%= link_to image_tag('next.gif', :border => 0), :action => :next %>
<%= link_to( "<img src=" + image_path('logo.png') + ' />', { :controller => 'main' } ) %>
<%= link_to( "<img src=" + image_path('logo.png') + ' style="float: right;" />', { :controller => 'main' } ) %>
[edit] How do I make an image submit button?
image_submit_tag('next.gif')
Not with submit_tag (image_path(...)).
[edit] text_field or text_field_tag??
text_field_tag field_name, options
text_field object_name, field_name, options
[edit] How do I make a UI for editing multiple obects of the same class, and having an 'Add another _' button?
http://rails.techno-weenie.net/question/2006/8/24/multiple-records-from-one-form
[edit] [Used by View or Controller] Workaround for Rails bug: url_for flattens multi-dimensional params hashes
http://dev.rubyonrails.org/ticket/4947 "URL rewriter should support deeply nested parameters"
Solution: http://marklunds.com/articles/one/314
def flatten_hash(hash = params, ancestor_names = [])
flat_hash = {}
hash.each do |k, v|
names = Array.new(ancestor_names)
names << k
if v.is_a?(Hash)
flat_hash.merge!(flatten_hash(v, names))
else
key = flat_hash_key(names)
key += "[]" if v.is_a?(Array)
flat_hash[key] = v
end
end
flat_hash
end
def flat_hash_key(names)
names = Array.new(names)
name = names.shift.to_s.dup
names.each do |n|
name << "[#{n}]"
end
name
end
def hash_as_hidden_fields(hash = params)
hidden_fields = []
flatten_hash(hash).each do |name, value|
value = [value] if !value.is_a?(Array)
value.each do |v|
hidden_fields << hidden_field_tag(name, v.to_s, :id => nil)
end
end
hidden_fields.join("\n")
end
[edit] How do I access the controller?
@controller gives you the actual controller instance
params[:controller] gives you its URL component/name
[edit] Can I call methods of the controller from my view?
You're not supposed to.
But it seems that you can by using @controller.
For example: <%= @controller.send(:some_protected_method?) %>
I think it's okay to do for debugging (only).
[edit] How to tie a yes/no select box to a boolean field in your model
View:
To default to whatever is in the model by default:
<%= select_tag "person[is_bald]", options_for_select(["Yes", "No"], selected = (@person.is_bald? ? "Yes" : "No")) %>
To default to "Yes" if the column is nil in the model:
<%= select_tag "person[is_bald]", options_for_select(["Yes", "No"], selected = ((@person.is_bald.nil? || @person.is_bald?) ? "Yes" : "No")) %>
Model:
def is_bald=(new)
if new.is_a? String
write_attribute :is_bald, (new == "Yes")
else
write_attribute :is_bald, new
end
end
[edit] content_for / CaptureHelper
[edit] Links
http://errtheblog.com/post/28 err.the_blog.find_by_title('Content for Whom?')
http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.3/lib/action_view/helpers/capture_helper.rb
[edit] capture(*args, &block)
Capture allows you to extract a part of the template into an instance variable. You can use this instance variable anywhere in your templates and even in your layout.
<% @greeting = capture do %> Welcome To my shiny new web page! <% end %>
[edit] content_for(name, content = nil, &block)
content_for("name") is a wrapper for capture which will make the fragment available by name to a yielding layout or template.
Example:
<% content_for("header") do %>
alert('hello world')
<% end %>
content_for :what / yield :what
"Beware that content_for is ignored in caches. So you shouldn’t use it for elements that are going to be fragment cached."
[edit] Caveat: content_for only works properly when called from the view or a helper called from the view
So if you have something in lib, or a model, that calls the helper "directly" ... it may assign this content to a @content_for_name variable in the wrong object!
[Haven't verified this theory.]
[edit] Caveat: The call to content_for must come before the call to yield
This displays "test!":
<% content_for :test do %> test! <% end %> <%= yield :test %>
This, however, displays nothing, because at the time that the yield is called, @content_for_test has not yet been set to anything (it evaluates to nil):
<%= yield :test %> <% content_for :test do %> test! <% end %>
Why this is a problem...
Template rendering appears to happen in the following order:
- yields
- current template
It's a problem with helpers, since the page is evaluated in the order it is laid out
<% content_for :b do %> Content for b <% end %> <% b = yield :b %> <h1>Section A</h1> <h1>Section B</h1> <%= b %>
[edit] resource_on_demand test 1
This:
app/views/layouts/t.rhtml <html> <head> <%= require_on_demand %> </head> <body> <%= yield %> </body> </html> app/views/t/index.rhtml <%= render :partial => 'partial' %> app/views/t/_partial.rhtml I am a partial! <% require_stylesheet 'my_stylesheet', 'media' => 'all' %>
Produced this:
<html> <head> <link href="/stylesheets/my_stylesheet.css?1178226968" media="all" rel="Stylesheet" type="text/css" /> </head> <body> I am a partial! </body> </html>
[edit] Using content_for to capture a string expression instead of a section of ERb
It looks like this can only be done outside of a view / ERb template.
When I put this in my view:
<% content_for(:foo1) do %>
foo1
<% end %>
<%= yield :foo1 %>
<% content_for(:foo2) { 'foo2' } %>
<%= yield :foo2 %>
I only got "foo1" as output, not "foo1 foo2" as expected/desired.
The reason for this is that it detected an ERb buffer (_erbout) and used capture_erb_with_buffer instead of capture_block.
# File vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb, line 56
56: def capture(*args, &block)
57: # execute the block
58: begin
59: buffer = eval("_erbout", block.binding)
60: rescue
61: buffer = nil
62: end
63:
64: if buffer.nil?
65: capture_block(*args, &block)
66: else
67: capture_erb_with_buffer(buffer, *args, &block)
68: end
69: end
That's usually not a problem, though, because when you're in an ERb template, you almost always want to use it to capture a section of your ERb template.
What if you want to call a helper from within one of your templates? Will it still have this problem? The answer is no, there is no problem.
Thus, I can do this:
# Helper:
module THelper
def prepare_a_greeting_for(name)
content_for(:greeting) { "Hello, #{name}!" }
end
end
# View:
<% prepare_a_greeting_for('Tyler') %>
...
<%= yield :greeting %>
and it will happily print out "Hello, Tyler!"
Interestingly, it looks like somebody started to provide another way to supply a string to content_for, but never quite finished. Although content_for has a content argument, it doesn't seem to do anything. When I do this:
<% content_for(:foo3, 'foo3') %> <%= yield :foo3 %>
I get this error:
ActionView::TemplateError (/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_view/helpers/capture_helper.rb:98:in `capture_block': You have a nil object when you didn't expect it!
def capture_block(*args, &block)
block.call(*args)
end
It looks like they added the content argument to content_for in release 1.2.3 but didn't actually add any code that uses it. How ... silly.
r5335 | david | 2006-10-22 23:41:11
Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets (closes #6454) [BobSilva/chrismear]
M trunk/actionpack/CHANGELOG
M trunk/actionpack/lib/action_view/helpers/asset_tag_helper.rb
M trunk/actionpack/lib/action_view/helpers/capture_helper.rb
M trunk/actionpack/test/template/asset_tag_helper_test.rb
svn diff --diff-cmd colordiff /home/tyler/code/rails/rel_1-2-3/actionpack/lib/action_view/helpers/capture_helper.rb -r 5334:5335
/home/tyler/code/rails/rel_1-2-3/actionpack/lib/action_view/helpers/capture_helper.rb
--- /home/tyler/code/rails/rel_1-2-3/actionpack/lib/action_view/helpers/capture_helper.rb (revision 5334)
+++ /home/tyler/code/rails/rel_1-2-3/actionpack/lib/action_view/helpers/capture_helper.rb (revision 5335)
@@ -89,7 +89,7 @@
# named @@content_for_#{name_of_the_content_block}@. So <tt><%= content_for('footer') %></tt>
# would be avaiable as <tt><%= @content_for_footer %></tt>. The preferred notation now is
# <tt><%= yield :footer %></tt>.
- def content_for(name, &block)
+ def content_for(name, content = nil, &block)
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
end
When would you want to use content_for to capture a string (rather than a section of a ERb template -- that is, what gets output to _erbout)? The resource_on_demand plugin (http://svn.devjavu.com/liquid/resource_on_demand/lib/resource_on_demand.rb) is a good example of this. It is a helper that calls another helper (javascript_include_tag), which returns a string...
def require_javascript( *list )
@required_javascripts ||= []
content_for( :on_demand_header_includes ){ javascript_include_tag( *(list - @required_javascripts ).uniq ) }
@required_javascripts |= list
end
[edit] content_for yield_with_default
graeme nelson: Dirty Views? Clean them up! (http://blog.imperialdune.com/2007/3/27/dirty-views-clean-them-up) (2007-03-27).
<html> <head> </head> <body> <div id="wrapper"> <div id="header"> <% yield :header %> </div> <div id="content"> <%= yield %> </div> <div id="sidebar"> <% yield :sidebar %> </div> <div id="footer"> <% yield :footer %> </div> </div> </body> </html>And in your view the following:
<% content_for :header do %> This would appear for the yield :header call <% end %> <% content_for :sidebar do %> This is the sidebar content <% end %> <% content_for :footer do %> This is the footer content <% end %> <%# the rest would just be the content for the plain yield call %> This is would be my main contentThis is nice, but I wanted to find a way to supply default content and this is what I came up with.
def yield_with_default(yield_on, content=nil, &block) yield_content = eval "@content_for_#{yield_on} || ''" if block_given?(&block) concat(yield_content.empty? ? capture(&block) : yield_content, block.binding) else Binding.of_caller do |binding| concat(yield_content.empty? ? content : yield_content, binding) end end endI placed this in my application_helper.rb file. And now I can add something like this to my view.
# with a block <% yield_with_default :header do %> This is my DEFAULT header content <% end %> # without a block <% yield_with_default :header, "This would be my default content" %>
[edit] Textilize with syntax highlighting
http://rails.techno-weenie.net/tip/2005/11/29/textilize_with_syntax_highlighting
[edit] Helpers
[edit] Helpers: Ditch the controller-specific helpers!
That's just too many files to expect people to open up, check, refer to, etc.! Too many files!
Here's an alternative, proposed by zerohalo at http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model :
I do find myself wondering whether I should put a helper that applies to a particular controller only in the controller file itself or in the helper file tied to the controller.
I sometimes wonder whether the controller-specific helper files aren’t redundant, and that those helpers which are controller-specific should go in the controller, with those that are global in the
application_helperfile. Then you don’t have to look in both the controller and its helper file for stuff. (Same for models.)
[edit] Helpers can take blocks!
[edit] [Open question] How do I make a helper function pretty-looking with lots of HTML like ERb templates?
Usually when I am motivated to make a helper it is by duplication in my view: I do the same thing twice and I'd rather call a method twice than have all that code duplicated twice. So the natural thing to want to do is to move that code -- that ERb code! -- from the view into the helper. Don't make me reformat it a lot; I want it to be ERb just like the view!
Can we use erb to create the helper function?? render_to_string()??
Could we use partials instead?
I hate building a string with "blah" + "blah" and return_value += "something"!
Ruby on Rails / Ugly helper function example
Maybe use render :inline?
# Renders "hello david"
render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }
[edit] Country select helper
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#M000401
[edit] View-level: Associations (has_many, has_and_belongs_to_many)
[edit] How do I create a multiple select box for use in editing a has_and_belongs_to_many relationship?
http://lists.rubyonrails.org/pipermail/rails/2006-April/036962.html :
class User < ActiveRecord::Base
# id, name fields
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
# id, name fields
has_and_belongs_to_many :users
end
class UserController < AbstractApplicationController
model :user
def edit
case @request.method
when :get
@user = User.find(params['id'])
@available_groups = Groups.find_all - @user.groups
when :post
@user = User.find(params['user']['id'])
@user.groups << Group.find(params['add_groups'])
redirect_to :action => 'show', :id => @user.id
end
end
end
views/user/edit.rhtml
<%= form_tag :action => 'edit' %>
<%= hidden_field 'user', 'id' %>
<select name="add_groups[]" multiple="multiple">
<%= options_from_collection_for_select @available_groups, 'id',
'name' %>
</select>
</form>
[edit] Layouts
[edit] [Caveats (category)] Have to pass the layout name as a string
Do this:
layout 'main'
If you do this:
layout :main
, you'll get an "undefined method main" error...
[edit] [Problem (category)]: Can you nest one layout within another layout?
Useful, for example, when you want an "outer layout" that's shared by all per-controller layouts. This "outer layout" would include site-wide stuff, like the <html> tags, including some stylesheets, etc.
[edit] Example
views/layouts/master_layout.rhtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>My site: <%= @title %></title> <%= stylesheet_link_tag "common", :media => "all" %> </head> <body> <%= yield %> </body> </html>
views/layouts/controller1_layout.rhtml (wrapped in master_layout):
<h1><%= @title %></h1> <%= yield %> <div id="Sidebar"> ... </div>
views/controller1/view1.rhtml (wrapped in controller1_layout)
[edit] How to call render/render_to_string from outside of a controller?
This problem was possed on http://lists.rubyonrails.org/pipermail/rails/2006-May/043642.html but I didn't see a solution posted there.
This does not work:
renderer = ActionController::Base.new
@my_string = renderer.render_to_string(:template => 'controller/template')
@my_string = renderer.instance_eval { render_to_string('controller/template') }
@my_string = renderer.send(:render_template,'controller/template')
@my_string = RendererController.new.custom_render(:template => 'controller/template')
Those give errors such as:
The error occured while evaluating nil.[]= /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:994:in `add_class_variables_to_assigns'"
http://rails.techno-weenie.net/forums/1/topics/281 had two possible solutions: curl the URL of the template (don't like that idea, because then your templates have to be public), or call ERB directly.
[edit] ERb (Embedded Ruby)
[edit] [Comments in ERb]
Using =begin/=end seems to work for commenting out sections of ERB files. (http://woss.name/2006/05/07/notes-from-a-rails-course/0)
[edit] [Open complaint] [Comments in ERb] Using # for comments inside of a one-line <%= %> ERb block causes an error!
Example:
Throws an error:
<%= #pagination_links @order_pages %>
Doesn't throw an error:
<%= #pagination_links @order_pages %>
It seems like it's trying to comment out the %> delimiter.
But that doesn't make sense to me -- the %> isn't Ruby code, so it shouldn't be trying to comment it out! The <% and %> only mark where the actualy Ruby begins and ends.
Update: I think the standard way to comment out stuff in ERb is <%# rather than <%=# :
<%# #pagination_links @order_pages %>
[edit] Rendering templates / calling helpers from outside of normal controller/web context
[edit] How do I access a helper from a controller or other non-view class (like -- gasp! -- a model)?
Inspired by: http://www.bigbold.com/snippets/posts/show/1799
Add this to your ApplicationController:
# Make it possible to call helpers (the presentation layer) from controllers.
def presentation
Presentation.instance
presentation.controller = self
presentation
end
class Presentation
include Singleton
include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper # provides html_escape, which is used by url_for
include ActionView::Helpers::UrlHelper
attr_accessor :controller
# add any other helpers you want to be able to use
end
[edit] What are legitimate uses of this?
Okay, so some may object to the use of a helper in a controller or model, but sometimes, I don't see any other way to do it...
These are the legitimate uses I've come across so far:
- You want to put more than just plain text in a flash message. For example, you want to include a link in the message.
flash.now[:error] = "Blah blah. Please visit #{presentation.link_to('this page', 'http://wherever.com')} for more information."- Or do you think that should go in a view instead (and use render_to_string)?
Please comment if you disagree with any of these or know of a better way, a way to avoid needing to do this.
[edit] Caveat (fixed): using link_to raises NoMethodError "The error occurred while evaluating nil.url_for"
Started out with this (nice and simple):
# Make it possible to call helpers (the presentation layer) from controllers.
def presentation
Presentation.instance
end
module Presentation
include Singleton
include ActionView::Helpers::TextHelper
include ActionView::Helpers::UrlHelper
# add any other helpers you want to be able to use
end
This is the code I had in my controller action:
flash.now[:notice] = "Please go to #{presentation.link_to('this page', :action => 'this_page')}."
But I got this error:
NoMethodError (You have a nil object when you didn't expect it! The error occurred while evaluating nil.url_for):
Here is the backtrace:
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_view/helpers/url_helper.rb:27:in `send' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_view/helpers/url_helper.rb:27:in `url_for' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_view/helpers/url_helper.rb:75:in `link_to' app/controllers/the_controller.rb:363:in `the_action'
This is the offending line: url_helper.rb:27:
url = @controller.send(:url_for, options, *parameters_for_method_reference)
So @controller is nil, is it? Ugh.
Normally that is an instance variable of the anonymous "view" class created by Rails (#<#<Class:0xb70d9334>:0xb70d930c>). In this case, however, it is trying to access it as an instance variable of ApplicationController::Presentation (#<ApplicationController::Presentation:0xb721a66c>).
Therefore, we need to make our Presentation class behave as closely as possible (or at least as closely as is necessary to get this to work) as the view class as possible. In particular, we need to cause it to have a @controller instance variable which evaluates to an actual controller (rather than nil).
At first I tried to make it so I could pass in a controller to the Presentation constructor:
def presentation
Presentation.instance(self)
end
class Presentation
include Singleton
def initialize(controller)
@controller = controller
end
end
, but it looks like Klass.instance() does not pass along its arguments to Klass#initialize, as I would expect it to.
So I resorted to this less elegant solution:
# Make it possible to call helpers (the presentation layer) from controllers.
def presentation
Presentation.instance
presentation.controller = self
presentation
end
class Presentation
include Singleton
include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper # provides html_escape, which is used by url_for
include ActionView::Helpers::UrlHelper
attr_accessor :controller
# add any other helpers you want to be able to use
end
How could we rewrite my example so that we didn't need to call a helper from a controller?
Easy enough. Just move that presentation code into a view "where it belongs" and render that view with a call to render_to_string:
flash.now[:notice] = render_to_string(:action => 'flash_message_for_foo_action', :layout => false)
flash_message_for_foo_action.rhtml:
Please go to <%= link_to('this page', :action => 'this_page') %>.
A ticket for what appears to be a similar problem: #6774 ((PATCH) console's helper object doesn't work with #link_to) - Rails Trac - Trac
[edit] Rails / Ajax
Rails / Ajax edit (Category edit)
[edit]
http://svn.tylerrick.com/public/rails/examples/ajax/ajax_examples/
[edit] You can't use Ajax.Updater to update a table in IE!
Let me clarify... The following code works just fine in Firefox, but does not work in IE:
app/views/test/ajax.rhtml:
<table id="table_without_tbody">
<tr id="tr1">
<td>
Static content
</td>
</tr>
</table>
<%= link_to_remote "add to :bottom of 'table'", :update => 'table_without_tbody', :url => {:action => 'an_ajax_action'}, :position => :bottom %>
app/controllers/test_controller.rb:
class TestController < ApplicationController
def an_ajax_action
render :layout => false
end
end
app/views/test/an_ajax_action.rhtml:
Content to be added via Ajax
Symptoms: Nothing happened when I clicked the link. The request showed up in the logs and there were no errors server side, but no changes in the DOM were visible. I didn't even get an alert saying "RJS Error" like some people have reported.
The solution is to update the tbody instead of the table directly:
app/views/test/ajax.rhtml:
<table id="table_with_tbody">
<tbody id="tbody">
<tr id="tr">
<td>
Static content
</td>
</tr>
</tbody>
</table>
<%= link_to_remote "add to :bottom of 'tbody'", :update => 'tbody', :url => {:action => 'an_ajax_action'}, :position => :bottom %>
<%= link_to_remote "add :after 'tr'", :update => 'tr', :url => {:action => 'an_ajax_action'}, :position => :after %>
References:
- Prototype Insertion, IE 6, tr’s, and “invalid target element for this operation…”
- IE fails to insert a table row using prototype
- innerHTML IE Problem
[edit] Ajax effects: Drag and drop
- a drag-n-drop demo (download) of acts_as_ordered_tree: svn://rubyforge.org/var/svn/ordered-tree/demo
- that was based on: http://ajaxonrails.wordpress.com/2006/11/26/ajaxonrailsdragdroptree/
[edit] Indicator icons
http://www.napyfab.com/ajax-indicators/ A bunch of (free -- I assume) animated indicator icons you can use to indicate that an Ajax call is currently in process / working in the background.
[edit] Since you apparently can't call render/render_to_string from outside of a controller, how do I render my templates?
You can use ERb directly, much like Rails does behind the scenes when it renders a template:
template = ERB.new(File.readlines('app/views/dir/my_view.rhtml').join(''), nil, '-')
html = template.result(binding)
[edit] how do I use helpers, like image_tag?
Story: (hide)
lib/some_class.rb: class SomeClass include ApplicationHelper include ActionView::Helpers::AssetTagHelper end
But then you get this error:
NoMethodError: undefined method `relative_url_root'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:156:in `compute_public_path'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:125:in `image_path'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:140:in `image_tag'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:
152 def compute_public_path(source, dir, ext)
153 source = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
154 source << ".#{ext}" unless source.split("/").last.include?(".")
155 source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
156 source = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
157 source = ActionController::Base.asset_host + source unless source.include?(":")
158 source
159 end
lib/some_class.rb: class FakeRequest def relative_url_root; "http://example.com"; end end class FakeController def request; FakeRequest.new; end end class SomeClass include ApplicationHelper include ActionView::Helpers::AssetTagHelper end
But then you get this error:
NoMethodError: undefined method `tag'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:148:in `image_tag'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:
148 tag("img", options)
Final version:
lib/some_class.rb: class FakeRequest def relative_url_root; "http://example.com"; end end class FakeController def request; FakeRequest.new; end end class SomeClass include ApplicationHelper include ActionView::Helpers::AssetTagHelper include ActionView::Helpers::TagHelper end
[edit] How do I access a helper from a controller or other non-view class (like -- gasp! -- a model)?
http://www.bigbold.com/snippets/posts/show/1799
Add this to your ApplicationController:
def help
Helper.instance
end
class Helper
include Singleton
include ActionView::Helpers::TextHelper
end
[edit] Rails / Ajax
Rails / Ajax edit (Category edit)
[edit]
http://svn.tylerrick.com/public/rails/examples/ajax/ajax_examples/
[edit] You can't use Ajax.Updater to update a table in IE!
Let me clarify... The following code works just fine in Firefox, but does not work in IE:
app/views/test/ajax.rhtml:
<table id="table_without_tbody">
<tr id="tr1">
<td>
Static content
</td>
</tr>
</table>
<%= link_to_remote "add to :bottom of 'table'", :update => 'table_without_tbody', :url => {:action => 'an_ajax_action'}, :position => :bottom %>
app/controllers/test_controller.rb:
class TestController < ApplicationController
def an_ajax_action
render :layout => false
end
end
app/views/test/an_ajax_action.rhtml:
Content to be added via Ajax
Symptoms: Nothing happened when I clicked the link. The request showed up in the logs and there were no errors server side, but no changes in the DOM were visible. I didn't even get an alert saying "RJS Error" like some people have reported.
The solution is to update the tbody instead of the table directly:
app/views/test/ajax.rhtml:
<table id="table_with_tbody">
<tbody id="tbody">
<tr id="tr">
<td>
Static content
</td>
</tr>
</tbody>
</table>
<%= link_to_remote "add to :bottom of 'tbody'", :update => 'tbody', :url => {:action => 'an_ajax_action'}, :position => :bottom %>
<%= link_to_remote "add :after 'tr'", :update => 'tr', :url => {:action => 'an_ajax_action'}, :position => :after %>
References:
- Prototype Insertion, IE 6, tr’s, and “invalid target element for this operation…”
- IE fails to insert a table row using prototype
- innerHTML IE Problem
[edit] Ajax effects: Drag and drop
- a drag-n-drop demo (download) of acts_as_ordered_tree: svn://rubyforge.org/var/svn/ordered-tree/demo
- that was based on: http://ajaxonrails.wordpress.com/2006/11/26/ajaxonrailsdragdroptree/
[edit] Indicator icons
http://www.napyfab.com/ajax-indicators/ A bunch of (free -- I assume) animated indicator icons you can use to indicate that an Ajax call is currently in process / working in the background.
