Friday, January 11, 2013

[Salesforce / Visualforce] jQueryUI + Dialog + Postback

I wanna show you what happens if you do a postback inside a modal jQuery UI dialog. This is a problem I had some times ago making this challenge on Cloudspokes.

We have a simple controller:

 
public class MyController
{
 public String value{get;set;}
 
 public void anAction()
 {
  if(value != null && value.length()>0)
   ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR ,'Variable set to ['+value+']'));
  else
   ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR ,'Variable not set'));
 }

}

And this is the Visualforce page:

<apex:page controller="MyController" id="myPage">
 
 <apex:includeScript value="{!URLFOR($Resource.UIJQuery1822, 'js/jquery-1.7.2.min.js')}"/>
 <apex:includeScript value="{!URLFOR($Resource.UIJQuery1822, 'js/jquery-ui-1.8.22.custom.min.js')}"/>
    <apex:stylesheet value="{!URLFOR($Resource.UIJQuery1822, 'css/ui-lightness/jquery-ui-1.8.22.custom.css')}"/>
 
 <script language="javascript">

  //escapes Visualforce ID for jQuery
  function esc(myid) {
           return '#' + myid.replace(/(:|\.)/g,'\\\\$1');
        }
        
   j$ = jQuery.noConflict();
   
  //opens the dialog
  function openDialog(dialogId)
  {
   j$(esc(dialogId)).dialog({title:'Dialog window', modal:true});
  }
  
  //copies value from an input to another
  function copyHiddenFields(fromId, toId)
  {
   j$(esc(toId)).val(j$(esc(fromId)).val());
  }
  
 </script> 
 
 <apex:form id="myForm">
 
  <apex:pageMessages />


  <apex:outputPanel id="__dialog" style="display:none;">
   
   <apex:inputtext value="{!value}"/>
   <apex:commandButton value="Submit" action="{!anAction}"/>
   
  </apex:outputPanel>
  <apex:commandButton value="Open popup" onClick="openDialog('{!$Component.__dialog}'); return false;"/>
 </apex:form>
 
 
</apex:page>
This is the result:



If you try to post back nothing happens.
This is because the dialog is detached from the body and in some way the command button is detached from the javascript that makes the postback.
The trick is to use an <apex:actionFunction> component:

  <apex:actionFunction action="{!anAction}" name="doPostback"/>
  <apex:outputPanel id="__dialog2" style="display:none;">
   <apex:outputPanel >
    <apex:inputtext value="{!value}"/>
    <apex:commandButton onclick="doPostback(); return false;" value="Submit"/>
   </apex:outputPanel>
  </apex:outputPanel>
  <apex:commandButton value="Open popup 2" onClick="openDialog('{!$Component.__dialog2}'); return false;"/>


Now the postback works but there is still a problem: the "value" field is never set: the postback doesn't send any field in the dialog.
To accomplish this, you have to use an helper hidden field and some javascript to copy the dialog field into the hidden field (which is outside the dialog):

  <apex:actionFunction action="{!anAction}" name="doPostback"/>
  <apex:inputHidden value="{!value}" id="inputHidden"/>
  <apex:outputPanel id="__dialog3" style="display:none;">
   <apex:outputPanel >
    <apex:inputtext id="inputText" value="{!value}"/>
    <apex:commandButton 
     onclick="copyHiddenFields('{!$Component.inputText}','{!$Component.inputHidden}'); doPostback(); return false;" 
     value="Submit"/>
   </apex:outputPanel>
  </apex:outputPanel>
  <apex:commandButton value="Open popup 3" onClick="openDialog('{!$Component.__dialog3}'); return false;"/>


Append this simple JS function in the "script" tags:

//copies value from an input to another
  function copyHiddenFields(fromId, toId)
  {
   j$(esc(toId)).val(j$(esc(fromId)).val());
  }


Now the postback sends the correct value to the controller.


I leave you with a song for your soul.