Saturday, 13 December 2014

Visualforce Markup and JavaScript Includes

When using JavaScript in Visualforce pages it is often useful to set the initial state via the Visualforce controller and then use JavaScript to manipulate the page layout and state based on user interaction.

Visualforce Markup and merge fields in JavaScript that are directly in the Visualforce page work fine - consider the following Visualforce page that uses unobtrusive JavaScript to attach click event handlers to the name elements of a table of opportunities. (Yes, I know this is a pretty contrived example!).

<apex:page standardController="Opportunity" recordSetVar="opps">
  <apex:includeScript value="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"/>
  <script>
  $(document).ready(function() {
    <apex:repeat value="{!opps}" var="opp">
      $('[id$="nameOppCol{!opp.id}"]').on('click',
        function()
    	{
    	   window.open('/{!opp.Id}',
    	               'Opportunity','height=500,width=800,left=100,top=100');
    	}
      );
    </apex:repeat>
  });
  </script>
  
  <apex:pageBlock title="Opps Table">
    <apex:pageblocktable value="{!opps}" var="opp">
  	  <apex:column headerValue="Name">
  	    <div id="nameOppCol{!opp.Id}">
    	  <apex:outputField value="{!opp.Name}" />
  	    </div>
  	  </apex:column>
  	  <apex:column headerValue="Amount">
  	    <div id="amountOppCol{!opp.Id}">
    	  <apex:outputField value="{!opp.Amount}"/>
    	</div>
  	  </apex:column>
  	  <apex:column headerValue="Close Date">
  	    <div id="closeDateOppCol{!opp.Id}">
  	      <apex:outputField value="{!opp.CloseDate}"/>
  	    </div>
  	  </apex:column>
    </apex:pageblocktable>
  </apex:pageBlock>
</apex:page>

The JavaScript relies on a couple of Visualforce markup components to set up the click handlers - an <apex:repeat> to iterate the opportunities and a merge field to access the id of the opportunity, which identifies the <div> element that the click handler should be bound to:

<apex:repeat value="{!opps}" var="opp">
  $('[id$="nameOppCol{!opp.id}"]').on('click',
    function()
    {
       window.open('/{!opp.Id}',
                   'Opportunity','height=500,width=800,left=100,top=100');
    }
  );
</apex:repeat>

The page displays a list of opportunities:

Screen Shot 2014 12 13 at 12 34 41

and clicking on an opportunity name opens a popup window to display the opportunity details:

Screen Shot 2014 12 13 at 12 36 06

However, when getting the page ready for a production environment, its always good practice to place the JavaScript into its own static resource file (its also better to minify it, but for ease of understanding I’m skipping that part), resulting in a Visualforce page that simply includes the static resource:

<apex:page standardController="Opportunity" recordSetVar="opps">
  <apex:includeScript value="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"/>
  <apex:includeScript value="{!$Resource.oppJS}"/>
  
  <apex:pageBlock title="Opps Table">
    <apex:pageblocktable value="{!opps}" var="opp">
  	  <apex:column headerValue="Name">
  	    <div id="nameOppCol{!opp.Id}">
    	  <apex:outputField value="{!opp.Name}" />
  	    </div>
  	  </apex:column>
  	  <apex:column headerValue="Amount">
  	    <div id="amountOppCol{!opp.Id}">
    	  <apex:outputField value="{!opp.Amount}"/>
    	</div>
  	  </apex:column>
  	  <apex:column headerValue="Close Date">
  	    <div id="closeDateOppCol{!opp.Id}">
  	      <apex:outputField value="{!opp.CloseDate}"/>
  	    </div>
  	  </apex:column>
    </apex:pageblocktable>
  </apex:pageBlock>
</apex:page>

 However, when accessing this page the click event handlers no longer work and inspecting the page shows JavaScript errors. Clicking through to the included resource shows that the Visualforce markup hasn’t been replaced with the appropriate HTML elements:

Screen Shot 2014 12 13 at 12 49 02

When the JavaScript markup is in the Visualforce page, it will be processed by the Visualforce rendering engine and replaced with HTML elements, text etc as required, before being delivered to the browser.

However, when the JavaScript is moved into a static resource and included, it bypasses the Visualforce rendering engine. The containing page is processed as usual and delivered to the browser, but only then are the referenced JavaScript resources included, so any Visualforce markup, merge fields etc remain in their literal form. The browser's JavaScript then attempts to process the JavaScript, hits the Visualforce markup and generates a syntax error. 

3 comments:

  1. I have seen this behaviour with even angular JS so what i do is have another page that only includes script and use apex:include .This way i have all my JS in one more Page .

    ReplyDelete
  2. Nice One!!. Is there any workaround to include Javascript with mergefields as static resource??

    ReplyDelete
    Replies
    1. Unfortunately not. I usually assign and merge field values to JavaScript variables in the page.

      Delete