2 minute read

I recently had the problem that I wanted to pass a variable from a Visualforce page to a Visualforce component and assign it to the component’s controller.

So far so good, no problems at all, but then I wanted the controller’s variable to be updated when the variable on the page is changed during a re-render.

The page looks like this

<apex:page controller="PageController">

    <apex:includeScript value="{!URLFOR($Resource.res,'lib/jquery/js/jquery-1.4.2.min.js')}" />
    
    <script type="text/javascript">
        var jq$ = jQuery.noConflict();
    </script>

    <apex:form >
    <!-- Page block with the display options and refresh button -->
    <apex:pageBlock >
        <apex:pageBlockButtons location="top">
            <apex:selectList id="cbMode" value="{!selectedMode}" size="1">
                <apex:selectOptions value="{!Modes}"/>
            </apex:selectList> 
            <apex:selectList id="cbDay" value="{!selectedDay}" size="1">
                <apex:selectOptions value="{!Days}"/>
            </apex:selectList> 
            <apex:commandButton value="Refresh" action="{!onRefresh}" rerender="mySection,pgMap" 
                oncomplete="jQuery(document).trigger('updateData');" status="status"/>
            <apex:actionstatus id="status" startText="reading...">
            </apex:actionstatus>
        </apex:pageBlockButtons> 

           <apex:panelGroup id="pgMap"> 
                <c:Map data="{!tourlist}" mode="{!selectedMode}"/>
            </apex:panelGroup>    
        </apex:pageBlockSection>   
    </apex:pageBlock>   
    
    </apex:form>
</apex:page>

and the component like this

<apex:component controller="SCMapController" selfClosing="true" rendered="true">

    <apex:attribute description="The actual data to show on the map"
                    required="false"
                    type="MapData[]"
                    name="data"
assignTo="{!mapData}"
/>

<apex:inputHidden value="{!data}" id="tourData" />

<script>
var tourData = {!mapDataConverted};
</script>
</apex:component>

I wanted to use an assignTo to assign the attribute to a variable in the controller, convert the content to a JS object string and then read the variable in a JS script part. This works perfectly fine for the first call of the page, but after pressing the Refresh button, that re-renders the component I still have the old data on the JS side.

Some debugging revealed that the changed data isn’t passed to the controller sigh

I finally used a different approach: I assign the variable to a hidden field, which serializes the variable to something like this:

[MapData:
   [color=4080A0, 
    email=a@b.de, 
    items=(MapDataItem:
                [GeoX=5.465409, 
                 GeoY=51.354023, 
                 infoAddress= Brakenstraat 36A 5555 CL Valkenswaard
                ], 
            MapDataItem:
                [GeoX=5.442988572135558, 
                 GeoY=51.29824967330397, 
                 infoAddress=Some data
            ]),
    name=Test]]

The basic idea is to use this string and convert it to something that can be used in JavaScript. My object is in fact a list of map data objects that contains some fields and a list of items.

/**
 * Convert the serialized version (string) of an sObject to an
 * JS object. You get the serialized sObject by assigning a data
 * structure to an hidden field for example
 *
 * @param data String of the serialized sObject
 * @return JS Object of the data
 */
function sObject2JSObject(data)
{
 data = data.replace(/^\[/,'');
 data = data.replace(/\]$/,'');
 data = data.replace(/\w+:\[/g, '[')
 data = data.replace(/\[/g,'{');
 data = data.replace(/\]/g, '}');
 data = data.replace(/=\(/g, ':[');
 data = data.replace(/\)/g, ']');
 data = data.replace(/=([^,}]*)([,}])/g, "='$1'$2");
 data = data.replace(/=/g, ':');
 data = "[" + data + "]";

 var tmpData;
 eval("tmpData=" + data);

 return tmpData;
} 

The JS function sObject2JSObject() accepts the serialized string and returns the JS object created from it. The function is far from complete, but may help other as a starting point…