Rails and Ext non-Ajax Signup Form with Password Confirmation

This is, uh, a technical post.

Probably there are others who want to do the same somewhat senseless thing: use Ext to do form validation while keeping a boring non-Ajax post-and-response. The bottom line is that Ext favors doing it the Ajax way, and the Ajax way isn’t that hard to set up with Rails (just handle the form submission as normal but return JSON or XML to signal success or failure). But if you’re like me and working on a deadline, there can be a cognitive burden to switching to Ajax posting that you might want to avoid. Paradoxically, you might find yourself wasting a lot of time trying to figure out how to do it the “old-fashioned” way. Well, here’s one working standard-submission Signup Form, with fancy validations and all the kinks worked out.

Here’s the top half of the file users/new.html.erb, which is nearly the same as the code generated by restful-authentication:

<% @user.password = @user.password_confirmation = nil %>
<%= error_messages_for :user %>
<div id="no-js-form">
    <% form_for :user, :url => users_path, :html => {:id => "signup-form"} do |f| -%>
    <p>
        <label for="login">
            Real Name
        </label>
        <br/>
        <%= f.text_field :name, :id => "signup_name_field" %>
    </p>
    <p>
        <label for="login">
            User Name
        </label>
        <br/>
        <%= f.text_field :login, :id => "signup_login_field" %>
    </p>
    <p>
        <label for="email">
            Email
        </label>
        <br/>
        <%= f.text_field :email, :id => "signup_email_field" %>
    </p>
    <p>
        <label for="password">
            Password
        </label>
        <br/>
        <%= f.password_field :password, :id => "signup_password_field" %>
    </p>
    <p>
        <label for="password_confirmation">
            Confirm Password
        </label>
        <br/>
        <%= f.password_field :password_confirmation, :id => "signup_password_confirmation_field" %>
    </p>
    <p>
        <label for="password_confirmation">
            Role
        </label>
        <br/>
        <%= f.select :role, [["consumer","consumer"],["vendor","vendor"]], :id => "signup_role_field" %>
    </p>
    <p>
        <%= submit_tag 'Sign up', :id => "signup_submit_button" %>
    </p>
    <% end -%>
</div>
<div id="js-form-panel">
</div>

The only differences are a div wrapping the form (“no-js-form”) and the “js-form-panel” at the end. You’re going to laugh at me, but this form is buzzword-friendly; it’s unobtrusive in an ugly way. If javascript is turned on, the form will work, and the following will fail:

<script type="text/javascript">
    /* 
     Thanks to:
     http://www.extjswithrails.com/2008_03_01_archive.html for standardSubmit tip (hard to find!)
     http://extjs.com/forum/showthread.php?t=23068 for password confirmation
     Anyone else I stole semantics from
     */
    // Look, I'm copying over the authenticity token to send in the JS-generated form. LOL!
    var authenticity_token = document['forms'][0]['authenticity_token'].value;
 
    Ext.onReady(function(){
        $('no-js-form').hide();
 
        var myForm;
 
        function submitHandler(){
            form = myForm.getForm();
            form_as_dom = form.getEl().dom;
            form_as_dom.action = form.url;
            form_as_dom.submit();
        }
        myForm = new Ext.form.FormPanel({
            monitorValid: true,
            standardSubmit: true,
            url: "/users",
            applyTo: "js-form-panel",
            title: "Signup as a New User",
            width: 310,
            autoHeight: true,
            items: [new Ext.form.TextField({
                allowBlank: false,
                msgTarget: 'side',
                name: "user[name]",
                id: 'js_signup_name_field',
                fieldLabel: "Real Name"
            }), new Ext.form.TextField({
                allowBlank: false,
                vtype: 'alphanum',
                msgTarget: 'side',
                name: "user[login]",
                id: 'js_signup_login_field',
                fieldLabel: "Username"
            }), new Ext.form.TextField({
                allowBlank: false,
                vtype: 'email',
                msgTarget: 'side',
                name: "user[email]",
                id: 'js_signup_email_field',
                fieldLabel: "Email"
            }), new Ext.form.TextField({
                allowBlank: false,
                inputType: 'password',
                vType: 'password',
                msgTarget: 'side',
                name: "user[password]",
                id: 'js_signup_password_field',
                fieldLabel: "Password"
            }), new Ext.form.TextField({
                fieldLabel: "Password Confirm:",
                allowBlank: false,
                inputType: 'password',
                name: "user[password_confirmation]",
                initialPasswordField: 'signup_password_field',
                vType: 'password',
                msgTarget: 'side',
                id: 'js_signup_password_confirmation_field',
                fieldLabel: "Confirm Password",
                validator: function(value){
                    return (value == document.getElementById("js_signup_password_field").value) 
|| "Your passwords do not match";
                }
            }), new Ext.form.Hidden({
                name: "authenticity_token",
                value: authenticity_token
            }), new Ext.form.Hidden({
                name: "user[role]",
                value: "consumer"
            }), ],
            buttons: [{
                handler: submitHandler,
                text: "Signup",
                formBind: true
            }]
        });
 
    });
 
</script>

The noteworthy steps are: first, I hide the ‘no-js-form’, then I copy the authenticity_token that gets generated by a rails form to put in the js-generated form. Then, standardSubmit : true is the config option that makes a FormPanel not submit as an XmlHttpRequest. The funny code in the submitHandler is getting the underlying form object and calling submit on it, but as I write this it doesn’t make sense why both would be necessary. Finally, formbind : true causes the submit button to be deactivated while there are failing validations, and there’s some handy code for making sure that the password_confirmation matches password (totally lifted from somewhere else, see above).