Oh my god. It's full of code!

Apex Javascript remoting. Web developers Rejoice!

Ok, so it is probably no secret, I am in fact not an awesome object oriented programmer. Apex does not come very naturally for me (although it is getting better) but I often found the integration between VisualForce and Apex to be clunky. Too many properties, weird methods that you don’t invoke by name (when I call data, it’s actually calling getData(), wtf) and other strange behaviors that make little sense to me. So when I head about javascript remoting, which allowed tight integration between javascript and apex, I was totally stoked. No more having to have actionmethods to try and invoke controller code and dealing with properties and all that jazz. For those of us with maybe not so much java/C# background but a fair amount of web development, javascript remoting feels very natural. Once you pair it with jQuery, life just keeps getting better.

Say for example I want to create a signup form to host on a Salesforce site. I can create a form just like I always would in HTML and javascript, then create an apex method that can take the form data and do whatever. The call is asynchronous so it lends itself well to being all ajax-y and cool. To get you started, here is a sample of a form that is really cool because if I want to capture more data, all I need to do is add the fields to the form (that have fieldnames that match those on my contact). My jQuery and apex are smart enough to figure out the rest on their own.

Here is the page code.

<apex:page standardStylesheets="false" expires="1" cache="false" showHeader="false" sidebar="false" id="page" controller="contactClasses">

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script src="http://jquery.bassistance.de/validate/jquery.validate.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script>

<link rel="stylesheet" type="text/css" media="screen" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/cupertino/jquery-ui.css" />
<link rel="stylesheet" type="text/css" media="screen" href="http://jquery-ui.googlecode.com/svn/branches/dev/themes/base/ui.button.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js" type="text/javascript"></script>

<script>
  
function signUp()
{
    if($("#signUpForm").valid())
    {
        $('#submit').attr('disabled', 'disabled');
        var str = $("form").serialize();
    
        contactClasses.registerUser(str , function(contactResult, event)
        {                
            if (event.status) 
            {
                 console.log(contactResult);
                if(contactResult.success == 'true' || contactResult.success == true)
                {
                      $('#statusBoxText').html('Thanks for registering!.' +contactResult.data);
                }
                else
                {
                     $('#statusBoxText').html('Looks like there was a problem registering you.' +contactResult.data);
                }
                
                $('#submit').attr('disabled', null);                
            } 
            
    
        }, {escape:true});                                         
    }
}

    
$().ready(function() {
    // validate the comment form when it is submitted
    $("#signUpForm").validate();
    
    //style the button
    $(".button").button();

    $( "#statusBox" ).dialog({
        autoOpen: false,
        show: "blind",
        hide: "explode"            
    });      
});
</script>
  <style type="text/css">
body {
    font-size: 62.5%;
}
label {
    display: inline-block;
    width: 100px;
}
legend {
    padding: 0.5em;
    margin-left:auto;
    margin-right:auto;
}
fieldset fieldset label {
    display: block;
}
#signUpForm {
    width: 500px;
    margin-left:auto;
    margin-right:auto;
    margin-top:25px;
}
#signUpForm label {
    width: 250px;
}
#signUpForm label.error, #commentForm button.submit {
    margin-left: 253px;
}
#signUpForm {
    width: 670px;
}
#signUpForm label.error {
    margin-left: 250px;
    width: auto;
    display: block;
}
</style>
  <form class="cmxform"  id="signUpForm" >
    <fieldset class="ui-widget ui-widget-content ui-corner-all">
      <legend class="ui-widget ui-widget-header ui-corner-all">Please provide your information in the form below to sign up</legend>
      <p>
        <label>Firstname</label>
        <input name="Firstname" id="Firstname" type="text" maxlegth="15" size="60" class="required"  />
      </p>
      <p>
        <label>Last Name</label>
        <input name="Lastname" id="Lastname" type="text" size="60"  class="required" />
      </p>
      <p>
        <label>Email</label>
        <input name="Email" id="Email"  type="text" size="60"  class="required email"/>
      </p>
      <p>
        <label>Phone</label>
        <input name="Phone" id="Phone" type="text" size="60" maxlength="12" alt="phone" class="required phone"/>
      </p>
      <p>
        <label>Mobile Phone</label>
        <input name="MobilePhone" id="MobilePhone" type="text" size="60" maxlength="12" alt="phone" class="required phone" />
      </p>
      <p>
        <label>Street Address</label>
        <input name="MailingStreet" id="MailingStreet" type="text" size="60" class="required" />
      </p>
      <p>
        <label>City</label>
        <input name="MailingCity" id="MailingCity" type="text" size="60" class="required" />
      </p>
      <p>
        <label>State</label>
        <input name="MailingState" id="MailingState" type="text" size="60" class="required" minlength="2" maxlength="2" />
      </p>
      <p>
        <label>Zip Code</label>
        <input name="MailingPostalCode" id="MailingPostalCode" type="text" size="60" class="required number" minlength="5" maxlength="5" />
      </p>
      <p>
        <label>Gender</label>
        <select name="Gender__c">
          <option>Male</option>
          <option>Female</option>
        </select>
      </p>
      <p>
        <label>Birth Date</label>
        <select id="Month" name="Month">
          <option value="0">--Select One--</option>
          <option value="1">January</option>
          <option value="2">February</option>
          <option value="3">March</option>
          <option value="4">April</option>
          <option value="5">May</option>
          <option value="6">June</option>
          <option value="7">July</option>
          <option value="8">August</option>
          <option value="9">September</option>
          <option value="10">October</option>
          <option value="11">November</option>
          <option value="12">December</option>
        </select>
        <select id="Day" name="Day">
          <option value="0">--Select One--</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
          <option value="4">4</option>
          <option value="5">5</option>
          <option value="6">6</option>
          <option value="7">7</option>
          <option value="8">8</option>
          <option value="9">9</option>
          <option value="10">10</option>
          <option value="11">11</option>
          <option value="12">12</option>
          <option value="13">13</option>
          <option value="14">14</option>
          <option value="15">15</option>
          <option value="16">16</option>
          <option value="17">17</option>
          <option value="18">18</option>
          <option value="19">19</option>
          <option value="20">20</option>
          <option value="21">21</option>
          <option value="22">22</option>
          <option value="23">23</option>
          <option value="24">24</option>
          <option value="25">25</option>
          <option value="26">26</option>
          <option value="27">27</option>
          <option value="28">28</option>
          <option value="29">29</option>
          <option value="30">30</option>
          <option value="31">31</option>
        </select>
        <select id="Year" name="Year">
          <option value="0">--Select One--</option>
          <option value="1926">1926</option>
          <option value="1927">1927</option>
          <option value="1928">1928</option>
          <option value="1929">1929</option>
          <option value="1930">1930</option>
          <option value="1931">1931</option>
          <option value="1932">1932</option>
          <option value="1933">1933</option>
          <option value="1934">1934</option>
          <option value="1935">1935</option>
          <option value="1936">1936</option>
          <option value="1937">1937</option>
          <option value="1938">1938</option>
          <option value="1939">1939</option>
          <option value="1940">1940</option>
          <option value="1941">1941</option>
          <option value="1942">1942</option>
          <option value="1943">1943</option>
          <option value="1944">1944</option>
          <option value="1945">1945</option>
          <option value="1946">1946</option>
          <option value="1947">1947</option>
          <option value="1948">1948</option>
          <option value="1949">1949</option>
          <option value="1950">1950</option>
          <option value="1951">1951</option>
          <option value="1952">1952</option>
          <option value="1953">1953</option>
          <option value="1954">1954</option>
          <option value="1955">1955</option>
          <option value="1956">1956</option>
          <option value="1957">1957</option>
          <option value="1958">1958</option>
          <option value="1959">1959</option>
          <option value="1960">1960</option>
          <option value="1961">1961</option>
          <option value="1962">1962</option>
          <option value="1963">1963</option>
          <option value="1964">1964</option>
          <option value="1965">1965</option>
          <option value="1966">1966</option>
          <option value="1967">1967</option>
          <option value="1968">1968</option>
          <option value="1969">1969</option>
          <option value="1970">1970</option>
          <option value="1971">1971</option>
          <option value="1972">1972</option>
          <option value="1973">1973</option>
          <option value="1974">1974</option>
          <option value="1975">1975</option>
          <option value="1976">1976</option>
          <option value="1977">1977</option>
          <option value="1978">1978</option>
          <option value="1979">1979</option>
          <option value="1980">1980</option>
          <option value="1981">1981</option>
          <option value="1982">1982</option>
          <option value="1983">1983</option>
          <option value="1984">1984</option>
          <option value="1985">1985</option>
          <option value="1986">1986</option>
          <option value="1987">1987</option>
          <option value="1988">1988</option>
          <option value="1989">1989</option>
          <option value="1990">1990</option>
          <option value="1991">1991</option>
          <option value="1992">1992</option>
          <option value="1993">1993</option>
          <option value="1994">1994</option>
          <option value="1995">1995</option>
          <option value="1996">1996</option>
          <option value="1997">1997</option>
          <option value="1998">1998</option>
          <option value="1999">1999</option>
          <option value="2000">2000</option>
          <option value="2001">2001</option>
          <option value="2002">2002</option>
          <option value="2003">2003</option>
          <option value="2004">2004</option>
          <option value="2005">2005</option>
          <option value="2006">2006</option>
          <option value="2007">2007</option>
        </select>
      </p>
      <p>
        <input type="button" onClick="signUp();" value="Create Account" class="button" />
 
      </p>
    </fieldset>
  </form>

  
    <div id="statusBox" title="Signup Complete!">
        <div id="statusBoxText"></div>
    </div>
</apex:page>

And the controller

global class contactClasses
{   
    global class remoteObject
    {
        public boolean success{get;set;}
        public string message{get;set;}
        public string data{get;set;}
        public list<sObject> sObjects {get;set;} 
    }

    @remoteAction
    global static remoteObject registerUser(string dataString)
    {
        remoteObject returnObj = new remoteObject();
        returnObj.success = true;
        returnObj.message = 'Member Created';
        returnObj.data = 'Booya';
        
        try
        {
            map<string,string> formData = deserializeString(dataString);
            list<Contact> contacts = new list<Contact>();
            Account thisAccount = new Account();
            thisAccount.name = formData.get('accountname');
            insert thisAccount;            
                        
            Contact thisContact = new Contact();
            thisContact.AccountId = thisAccount.id;

            
            for(String fieldName : formData.keySet())
            {
                try
                {
                    thisContact.put(fieldName,formData.get(fieldName)); 
                }
                catch(Exception e)
                {
                    system.debug('Field: ' +fieldName+' does not exist on contact');
                }          
            }
            contacts.add(thisContact);
            
            insert thisContact;
            returnObj.data = thisContact.id;
            returnObj.sObjects = contacts;
        }
        catch(exception e)
        {
            returnObj.success = false;
            returnObj.data = e.getMessage();
        }
        
        
        return returnObj;       
    }

    public static Map<string,string> deserializeString(String argString)
    {     
        string[] params = argString.split('&');
        map<String,String>  formParams = new map<String,String>();
        for(string p : params)
        {        
            formParams.put(EncodingUtil.urlDecode(p.substring(0,p.indexOf('=')),'UTF-8'),EncodingUtil.urlDecode(p.substring(p.indexOf('=')+1,p.length()),'UTF-8'));    
        }        
        return formParams;
    }  
            
    public class customException extends Exception {}
}

So what’s happening here? Well in the page we create a form and style it with jQuery themes. Then we apply some validations using a nifty plugin to make sure the users don’t enter trash data. Nothing too special there. Then we move into where it gets fun. Once you click the submit button, your information is validated. If the validation passes, the entire form gets serialized, meaning all the values get turned into a string. That string is passed as a single argument to the controller using javascript remoting. The string is received and turned into a map by my neato deserializeString method (if there is a built in apex method to do this, I don’t know about it).

So once that method runs we have a map of field names to field values (just like how the original form was). We simply iterate over the values in the map, and attempt to add them to a contact object. If it errors, that means that field is either of the wrong type, or that it doesn’t exist on the contact at all (now you see why it is important to name your fields exactly as they appear on the contact object). Once the iteration is done, we insert the contact.

The last part is returning the data. I created a simple data type called a remoteObject. It’s just an object that contains a boolean success key, a key to hold a message, a key to contain some extra data, and a key that can contain a list of any kind of sObject. The great thing here is that when it gets returned, javascript knows how to handle it. It is able to access the keys, iterate over the sObjects, all that. The only weird thing is the boolean can’t seem to make up it’s mind sometimes if it’s an actual boolean value, or a string that contains true or false. I just test for both.

In your callback function of the remoting call you can evaluate the results and do whatever you like. In this case, I display a simple dialog confirming the results. There you have it, a process that feels much more natural for those of us who may not quite dig the standard VF/Apex design pattern. It’s also fast as hell, like probably faster than the ‘regular’ approach. I’m pretty sure I’m never going to write a a page the old way again.

Anyway, hopefully this helps some people get on their way with javascript remoting. The tools and examples here should make things a lot easier. Especially the trick about serializing a form, then deserializing it on the other end. No need to declare each parameter and do all that painful evaluation for each variable. I know I am digging it anyway 😛

One response

  1. ken

    Dan, thanks for the post. It is great that you share your thinking and examples as you are learning this stuff. I have shared your posts (including this one) with my team, and even with a customer circle or three on Google+. I hang with a bunch of lurkers, so I wanted to give you a shout and some positive feedback.

    Though not getting comments doesn’t stop me from posting and writing what I want to write, it is nice to know somebody is out there, right?

    –k

    July 23, 2011 at 12:51 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s