Topic: form issue: select box - onChange should display page differently

hello. i'm trying to learn rails and made this simple blog with entries, users and topics.
on page 'entry/list' i'd like to display only the entries that belong to a certain topic which can be chosen from a select box. somehow all entries are shown no matter which topic i choose.
this is my 'list.rhtml':

<html>
    <head>
        <title>blog - all entries</title>
    </head>
    <body>
        <h1>blog - all entries</h1>       
        <form action="<% self %>" method="post" name="topiChange">
            <p>topic (current id: <%= @topic %>)<br/></p>
            <select name="top" onChange="this.form.submit(document.topiChange.top.value)">
                <% @topics.each do |topic| %>
                    <option
                        <% if(topic.id==@topic) %>
                            <%= "selected" %>
                        <% end %>
                        value=<%=topic.id %>>
                        <%= topic.title %>
                    </option>
                <% end %>
            </select>
        </form><br/>
        <% @entries.each do |entry| %>
            <% if (@topic == nil) || (@topic == 1) || (@topic == entry.topic.id) %>
                <% dt=entry.date.to_a %>
                <% dat=dt[3].to_s+"."+dt[4].to_s+"."+dt[5].to_s+" at "+dt[2].to_s+":"+dt[1].to_s+":"+dt[0].to_s %>
                <p>
                    <%= link_to entry.title, :action => "show", :id => entry.id %> by <b><%= entry.user.name %></b> on <%= dat %>
                </p>
            <% end %>
        <% end %>
        <p><br/><%= link_to "create new", :action => "new" %></p>
    </body>
</html>

...and this is my 'entry_controller.rb':

class EntryController < ApplicationController
  scaffold:entry
 
  def list(*top)
    @entries=Entry.find_all(@params["date"])
    @topics=Topic.find_all(@params["title"])
    ar=[2...@topics.length]
    if (top.length==1 && top===ar)
  #  if (top.length==1 && top<@topics.length && top>1)
      @topic=top[0]
    else
      @topic=1
    end
  end
 
  def new
    @entry=Entry.find_all(@params["id"])
    @topics=Topic.find_all(@params["id"])
    @users=User.find_all(@params["name"])
  end
 
  def create
    @entry = Entry.new(@params['entry'])
    if @entry.save
        redirect_to :action => 'list'
    else
        render_action 'new'
    end
  end

  def show
    @entry=Entry.find(@params["id"])
  end
 
  def delete
    Entry.find(@params['id']).destroy
    redirect_to :action => 'list'
  end
end


...thanks in advance

Re: form issue: select box - onChange should display page differently

Perhaps this is what you want:

  def list(*top)
    @topics=Topic.find_all(@params["title"])
    ar=[2...@topics.length]
    if (top.length==1 && top===ar)
  #  if (top.length==1 && top<@topics.length && top>1)
      @topic=top[0]
    else
      @topic=1
    end
    @entries = Entry.find_all_by_topic_id(@topic)
  end

This code could use some serious cleaning up though. I can give you some suggestions on doing that if you want.

Railscasts - Free Ruby on Rails Screencasts

Re: form issue: select box - onChange should display page differently

thank you, i get your point but i'm afraid the problem is something else. the value sent by the 'topiChange'-form doesn't seem to get to the 'list'-method. the post-variable called 'top' is always empty and the value of '@topic' is set to '1' so all entries i see are those that belong to topic#1.
and yes, if you could find the time i'd very much appreciate suggestions on how to clean up my code a bit. thanks again...

Last edited by drumboy (2007-01-12 08:38:50)

Re: form issue: select box - onChange should display page differently

Alright, I think after cleaning up the code the problem will go away, or at least be easier to fix.

Just to make sure I'm understanding this code, you are displaying a list of entries with a topic select box. Upon choosing a topic you want the page to update the list of entries.

Let's focus on cleaning up the select box:

<form action="<% self %>" method="post" name="topiChange">
    <p>topic (current id: <%= @topic %>)<br/></p>
    <select name="top" onChange="this.form.submit(document.topiChange.top.value)">
        <% @topics.each do |topic| %>
            <option
                <% if(topic.id==@topic) %>
                    <%= "selected" %>
                <% end %>
                value=<%=topic.id %>>
                <%= topic.title %>
            </option>
        <% end %>
    </select>
</form>

We can make this a lot simpler by taking advantage of form helpers in Rails. Specifically, the select_tag. We can combine that with options_from_collection_for_select to make something nice and simple. The javascript can be shortened too.

<form action="<% self %>" method="post" name="topiChange">
    <p>topic (current id: <%= @topic %>)<br/></p>
    <%= select_tag :top, options_from_collection_for_select(@topics, :id, :title, @topic),
                   :onchange => "this.form.submit()" %>
</form>

We can use the start/end_form_tag helpers to clean it up a bit more:

<%= start_form_tag %>
    <p>topic (current id: <%= @topic %>)<br/></p>
    <%= select_tag :top, options_from_collection_for_select(@topics, :id, :title, @topic), :onchange => "this.form.submit()" %>
<%= end_form_tag %>

One more thing, there's no way to select an "all topics" option. We can do this with content_tag helper and just add it to the other options.

<%= start_form_tag %>
    <p>topic (current id: <%= @topic %>)<br/></p>
    <%= select_tag :top, (content_tag(:option, 'All Topics') + options_from_collection_for_select(@topics, :id, :title, @topic)), :onchange => "this.form.submit()" %>
<%= end_form_tag %>

That line is ridiculously long now. You can move part of it into a helper method to clean it up.

# in helpers/entry_helper.rb
def topic_options(topics, selected)
  content_tag(:option, 'All Topics') + options_from_collection_for_select(topics, :id, :title, selected)
end

# in list.rhtml
<%= start_form_tag %>
    <p>topic (current id: <%= @topic %>)<br/></p>
    <%= select_tag :top, topic_options(@topics, @topic), :onchange => "this.form.submit()" %>
<%= end_form_tag %>


Much better. It might take some time to get used to helper methods like this, but it can make code a lot cleaner.

Now onto the entries list:

<% @entries.each do |entry| %>
    <% if (@topic == nil) || (@topic == 1) || (@topic == entry.topic.id) %>
        <% dt=entry.date.to_a %>
        <% dat=dt[3].to_s+"."+dt[4].to_s+"."+dt[5].to_s+" at "+dt[2].to_s+":"+dt[1].to_s+":"+dt[0].to_s %>
        <p>
            <%= link_to entry.title, :action => "show", :id => entry.id %> by <b><%= entry.user.name %></b> on <%= dat %>
        </p>
    <% end %>
<% end %>

First of all, we can just remove the if condition. We can limit the entries in the controller instead.

<% @entries.each do |entry| %>
    <% dt=entry.date.to_a %>
    <% dat=dt[3].to_s+"."+dt[4].to_s+"."+dt[5].to_s+" at "+dt[2].to_s+":"+dt[1].to_s+":"+dt[0].to_s %>
    <p>
        <%= link_to entry.title, :action => "show", :id => entry.id %> by <b><%= entry.user.name %></b> on <%= dat %>
    </p>
<% end %>

Next is that date line. There's this method called strftime in the Time class (which I'm assuming this is) which can be used to format a time.


<% @entries.each do |entry| %>
    <p>
        <%= link_to entry.title, :action => "show", :id => entry.id %>
        by <b><%= entry.user.name %></b>
        on <%= entry.date.strftime("%d.%m.%Y at %H:%M:%S") %>
    </p>
<% end %>

Next the controller:

def list(*top)
  @entries=Entry.find_all(@params["date"])
  @topics=Topic.find_all(@params["title"])
  ar=[2...@topics.length]
  if (top.length==1 && top===ar)
#  if (top.length==1 && top<@topics.length && top>1)
    @topic=top[0]
  else
    @topic=1
  end
end

One issue here is the top variable isn't passed as a parameter to the list method. Instead you can access it with params[:top]. This holds the id of the selected topic. First the @topic and @topics variables need to be set:

def list
  @topic = params[:top]
  @topics = Topic.find(:all, :order => 'title')
  #...
end

The @entries variable still needs to be set. If the @topic variable is valid, we want to find the entries for that topic, otherwise find all entries.

def list
  @topic = params[:top]
  @topics = Topic.find(:all)
  if @topic.blank?
    @entries = Entry.find(:all)
  else
    @entries = Entry.find_all_by_topic_id(@topic)
  end
end

Here's the final code with a couple more minor changes.

# in entry_controller.rb
def list
  @topics = Topic.find(:all)
  if params[:topic_id].blank?
    @entries = Entry.find(:all)
  else
    @entries = Entry.find_all_by_topic_id(params[:topic_id])
  end
end

# in entry_helper.rb
def topic_options(topics, selected)
  content_tag(:option, 'All Topics') + options_from_collection_for_select(topics, :id, :title, selected)
end

# in list.rhtml
<html>
    <head>
        <title>blog - all entries</title>
    </head>
    <body>
        <h1>blog - all entries</h1>
       
        <%= start_form_tag %>
        <p>topic (current id: <%= params[:topic_id] %>)<br/></p>
        <%= select_tag :topic_id, topic_options(@topics, params[:topic_id]), :onchange => "this.form.submit()" %>
    <%= end_form_tag %>
   
        <% @entries.each do |entry| %>
        <p>
            <%= link_to entry.title, :action => "show", :id => entry.id %>
            by <b><%= entry.user.name %></b>
            on <%= entry.date.strftime("%d.%m.%Y at %H:%M:%S") %>
        </p>
    <% end %>
   
        <p><br/><%= link_to "create new", :action => "new" %></p>
    </body>
</html>


I haven't really tested any of this, so hopefully it all works without any problems. I encourage you to clean the code in the other controller actions too. Hope this helped.

Railscasts - Free Ruby on Rails Screencasts

Re: form issue: select box - onChange should display page differently

thank you.
it's amazing how little code you need...
i think i've learned a lot from your very detailed answer.

...nevertheless it still doesn't work the way i want it to.

in 'entry_helper.rb' we defined 'topic_options' with the parameter 'selected' that is set to 'params[:topic_id]' but the selected topic is always #0.

any idea how come?

Re: form issue: select box - onChange should display page differently

Oh, I keep forgetting this. The params[:topic_id] returns the selected topic id in a String. But, the id in the topic model is actually an Integer. So, the string needs to be converted before it can work. Try this:

# in entry_helper.rb
def topic_options(topics, selected)
  content_tag(:option, 'All Topics') + options_from_collection_for_select(topics, :id, :title, selected.to_i)
end

Railscasts - Free Ruby on Rails Screencasts

Re: form issue: select box - onChange should display page differently

I just wanted to say thanks for this.  I was running into the same problem (my selected choice not being selected).  I probably would have spent hours before figuring out I needed "to_i" (if I ever did).

Thanks again!