Wolfmans Howlings

A programmers Blog about Ruby, Rails and a few other issues

How to protect a form from accidentally losing data

Posted by Jim Morris Wed, 15 Nov 2006 06:47:44 GMT

Here is a good one, your users are complaining that they have spent 5 minutes filling in a web form, then lose it all by clicking on a link away from the form, and back arrow doesn't restore the fields that were entered. (At least on IE).

I saw an interesting design in the Rails Recipes book for saving form data at regular intervals, but I didn't like the overhead of that one.

So I found a snippet of javascript that detects when you are about to switch away from a page, I coupled that with a simple observer that sets a dirty flag when any field is modified in anyway, and you get something that pops up a dialog asking if they really want to leave this page and lose their input data, they get the option to not go to the new page and submit the form instead.

This is not as clean as I'd like, so anyone with ideas of making it cleaner please let me know.

This would go in app/helpers/application.rb...

  # if the given form has changed at all then pop-up a dialog if they
  # try to move away from the form before saving
  # You have to add :html => {:id => 'new_mission_form'} to the form
  # and  :onclick => "dirty_page= false;" to the submit button
  def catch_page_change_if_changed(form, obj= nil)
    <<-END
      <script language="javascript">
          <!--
            dirty_page = #{obj.nil? || obj.errors.empty? ? "false" : "true"};
            new Form.EventObserver(#{form}, function(element, value) {dirty_page= true; });
            window.onbeforeunload = function (evt) {
              if (dirty_page) {
                var message = 'Leaving this page will lose all entered values';
                if (typeof evt == 'undefined') {
                  evt = window.event;
                }
                if (evt) {
                  evt.returnValue = message;
                }
                return message;
              }
            }
         //-->
      </script>
    END
  end

In the view you give the form an id, and you also need to add an :onclick to the submit button so it allows you to submit the form without popping up the dialog...

<% form_for :obj, @obj,  :html => {:id => 'new_form'}, :url => { :action => 'create' } do |f| %>
...
<%= submit_tag "Submit", :onclick => "dirty_page= false;" %>

then call this somewhere in that view...

catch_page_change_if_changed('new_form', @obj)

Basically I just set a javascript variable called dirty_page to false, and have the observer set it to true if anything changes. The optional object being passed in to the helper is used to detect if we are back on the form due to input errors, in which case we set the dirty_flag to true, so we don't lose the fields that were not in error.

Oh this only works if javascript is enabled (of course!)

Posted in  | Tags ,  | 2 comments | no trackbacks

Setting the focus in a form

Posted by Jim Morris Sun, 29 Oct 2006 23:40:38 GMT

This is a simple one, how do I set the focus in the first item in my form?

Put this in your app/helpers/application_helper.rb:

  # put this in the body after a form to set the input focus to a specific control id
  def set_focus_to_id(id)
    <<-END
    <script language="javascript">
        <!--
                document.getElementById("#{id}").focus()
        //-->
    </script>
    END
  end

Then in your .rhtml file somewhere after the form is defined add this

<%= set_focus_to_id 'user_login' %>

where user_login will be the id of the field you want to get the focus.

An example of a login form...

<p>
Please Login
</p>
<% form_for :user do |f| %>
  <label for="user_login">Login</label>
  <%= f.text_field :login, :tabindex => "1" %>
  <br />
  <label for="user_password">Password</label>
  <%= f.password_field :password, :tabindex => "2" %>
  <br />
  <label for="user_remember_me">Remember me:</label>
  <%= f.check_box :remember_me, :tabindex => "0" %>
  <br />
  <%= submit_tag 'Log in', :tabindex => "3" %>
  <br />
<% end %>

<%= set_focus_to_id 'user_login' %>

When the login form is shown the focus will be set to the login text field. Of course java script needs to be enambled otherwise they will have to do it manually.

Posted in  | Tags , ,  | 4 comments | no trackbacks

Flash problems with periodically_call_remote

Posted by Jim Morris Wed, 25 Oct 2006 21:08:38 GMT

I just ran into this little problem which kept me scratching my head for quite a while. My flash message didn't show up on the next action.

I have a status page which polls a database for status using periodically_call_remote, when the database reaches a certain state I show a button which says the process is complete go to next step.

The go to next step calls a controller action which does some processing that can take a few seconds, during that call any errors are dutifully written to flash like flash[:error]= "I got an error!" then does a redirect_to the listing page, where I expect to see the error flash at the top of the page.

This worked fine under development however under production on the production server I did not see those flash errors, so the user was like "duh what happened??".

I could see the errors logged in the production log so I knew the flash was getting set, so what was happening????

Well to cut a long story short, while the controller was processing the next action (which can take a few seconds), the periodically_call_remote can fire a few more times, this consumes the flash, so when it gets to the redirect_to page the flash no longer shows up. In development mode the timing is different so the status page does not fire again before the listing page shows up so the flash is displayed. Did I mention I hate race conditions :)

The solution is trivial, stick a flash.keep in the action that is called by the periodically_call_remote, and the flash is preserved for the redirect_to action. Problem solved!

Posted in  | Tags , ,  | no comments | no trackbacks

Getting a record id from text_field_with_auto_complete

Posted by Jim Morris Mon, 23 Oct 2006 20:58:46 GMT

I ran into this problem a few times,and I have seen others asking the same question, if you use text_field_with_auto_complete and the selection list returns non-unique results, how do you reference the actual record in the database you want?

For instance if you have text_field_with_auto_complete :customer, :name then in your controller: name= params[:customer][:name] and Customer.find_all_by_name(name) returns more than one entry you need to be a little more tricky to retrieve the actual record you wanted to select.

There are a couple of ways to do it.

One method is mentioned here:

http://www.dalemartenson.com/blog/?p=24 which hides the id field being returned.

Another method is here http://ricardo.pacheco.name/blog/articles/2006/09 which uses javascript to write the id into a hidden_field.

This wiki entry (about half way down) suggests another way to do it, putting the id in the id tag of the <li> andf fetching it with javascript.

Another method I use is to put in the text_field a string like "23,Blogs,Fred", this is the id of the customer record, and the last,first name. Then I do this in the controller method that receives the form data (eg def create) ...

namecsv= params[:customer][:name]
id,last,first= namecsv.split(',')
customer= Customer.find(id)

Although in reality I usually write a setter in the Model to handle the csv so I can simply pass the entire params to the model ie Customer.new(params)

I get the namecsv in the text box using this partial for the auto completer...

<ul class="auto_complete">
<% for customer in @customers do -%>
   <li class="big">
     <div class="name"><%=h customer.fullname -%></div>
     <div class="code"><%=h "#{customer.id},#{customer.lname},#{customer.fname}" -%></div>
     <div class="email">
       <span class="informal"><%=h "#{customer.email}" -%></span>
     </div>
   </li>
<% end -%>
</ul>

using this in the view...

<% text_field_with_auto_complete( :customer, :name, {}, {:select => 'code', :skip_style => true) %>

Notice the :select => 'code', this is critical as it tells it which part of the popup list to put into the text_field.

This is a little ugly and error prone so you need some error checking etc. The other method looks nicer on the screen but is more work in the background.

Posted in  | Tags ,  | 1 comment | no trackbacks

Older posts: 1 2 3 4 5 6