0

So, I have some code in which setTimeout is not working as wanted. I need getParameters() to run (which is a method in my Apex controller). This method needs to finish running which is where the values of buttonClicked and bDialogParameters come from. Finally, I need the BIGIANT_DIALOG.init(this, "Redeem", parameters, "{!$Api.Session_ID}") function to run which uses the 'parameters' JavaScript variable.

The BIGIANT_DIALOG.init(this, "Redeem", parameters, "{!$Api.Session_ID}") actually opens up a Modal dialog. I have added setTimeout calls in random places. I have messed around with quotes with no luck either.

For some reason, the setTimeout closes the Modal dialog as soon as it opens after 6 seconds. Can anyone tell me why?

Snippits from VF Page

<apex:commandButton value="{!button}" onclick="javascript:RedeemActionButton(this); return false;" rerender="hiddenBlock" styleClass="btn btn-primary nbtn" >
    <!-- Allows for retrieval of SAN for a given row where a button is pressed -->
    <apex:param name="rowIndex" assignTo="{!rowCount}" value="{!rowCount}"/>
    <!-- The type of button that was clicked (e.g. Redeem) -->
    <apex:param name="buttonClicked" assignTo="{!buttonClicked}" value="{!button}"/>
    <!-- Hidden pageBlock allows for the param above to be passed within a commandButton -->
    <apex:pageBlock id="hiddenBlock" rendered="false"/>
</apex:commandButton>

<!--- To be used to get values for bDialog Modal creation. Called from javascript function -->     
<apex:actionFunction action="{!calcBDialogParameters}" name="getParameters"/>

function RedeemActionButton(button) {
    setTimeout('getParameters()', 6000); <!-- call to Apex:actionFunction which will set the values below in the Apex controller -->
    var recordName="{!buttonClicked}"; <!-- retrieve value from Apex controller -->
    var parameters="{!bDialogParameters}"; <!-- retrieve value from Apex controller -->
    window.alert(recordName); <!-- Test to see what recordName is currently set to. Always shows up empty -->
    window.alert(parameters); <!-- Test to see what parameters is currently set to. Always shows up empty -->

    setTimeout(function(){BIGIANT_DIALOG.init(this, "Redeem", parameters, "{!$Api.Session_ID}")}, 6000); <!-- Opens a Modal dialog -->
}

7
  • Not sure of your architecture at all. Have you looked at javascript remoting and using callbacks to perform functions after responses are received?
    – Eric
    Jun 30, 2016 at 0:47
  • No, but I will look into it. Thanks!
    – Kevin
    Jun 30, 2016 at 0:48
  • @Kevin getParameters is an actionFunction? Does it reRender? I'm guessing your modal is being wiped out by DOM changes in Visualforce...
    – sfdcfox
    Jun 30, 2016 at 1:23
  • @sfdcfox -- I have added some additional code. Yes, getParameters() is an actionfunction and it rerenders a pageblock. It does this because it would otherwise not be able to pass the apex:params .
    – Kevin
    Jun 30, 2016 at 1:28
  • I added my answer which I've been working on for a bit (clearly). I see some other info that might change mine slightly, so take mine with a grain of salt for now. I'll update it if necessary shortly.
    – drakored
    Jun 30, 2016 at 1:40

1 Answer 1

3

TL;DR;

The variables aren't rendered yet when they're accessed, and even if they were they're not rendered in the current running version of the function. See a solution/flow below for a way around this problem.

The Problem

The problem is the flow. The function RedeemActionButton is rendered during the last pass of the Visualforce rendering engine (during rerender or initial render), and put into the browser's memory and a reference to it stored to the Window object for calling (window.RedeemActionButton).

When this function is called it is calling your apex:actionFunction synchronously, which then performs async call to the controller/extension and then re-renders the page/specified sections, and during this time, the other function that kicked off that async apex:actionFunction has already completed running.

Even if the re-render were to update the definition of the function while it is running, it's already been pulled into memory while running and you're only really redefining the function variable's reference.

Imagine this function if you will:

var x = function(){
  x = function() { console.log('horse'); };
  console.log('cow');
}

On first run it will output cow. On second run it'll output horse, even though it was edited at the start of the first run. It can't edit itself mid-run, and the same is true for the actionFunction's re-render of the function.

A Solution

To work around this you can employ a few tricks by using rerender and oncomplete on the actionFunction. By using oncomplete and re-rendering the function you're about to call, you can call to another method in Javascript and reference variables that were recently refreshed, and the method is only called after those variables are populated.

<apex:actionFunction name="getParameters" rerender="getparamscomplete" oncomplete="getParametersCompleted();"/>
<apex:outputPanel id="getparamscomplete">
    <script type="text/javascript">
    function RedeemActionButton(button) {
        // left your timeout, but potentially not needed unless you need a 6 second delay for some reason before executing all of this
        setTimeout('getParameters()', 6000); <!-- call to Apex:actionFunction which will set the values below in the Apex controller -->
    }
    function getParametersCompleted() {
        var recordName="{!JSENCODE(buttonClicked)}"; <!-- retrieve value from Apex controller -->
        var parameters="{!JSENCODE(bDialogParameters)}"; <!-- retrieve value from Apex controller -->
        window.alert(recordName); <!-- Test to see what recordName is currently set to -->
        window.alert(parameters); <!-- Test to see what parameters is currently set to -->

        // also possibly/probably not necessary unless you want another 6 second wait until the dialog loads after the apex is done loading those values above
        setTimeout(function(){BIGIANT_DIALOG.init(this, "Redeem", parameters, "{!$Api.Session_ID}")}, 6000); <!-- Opens a Modal dialog -->
    }
    </script>
</apex:outputPanel>

Additional Info

You can also do this without calling a second method in the oncomplete by actually rerendering the actionFunction itself (and storing the code in the oncomplete). I find this to be a little more clear though and recommend doing it this way.

You might also notice I added JSENCODE around your variables coming from the controller/extension. In JS this is critically important to prevent someone from hacking your page if those variables can be edited (via query params or stored data, or just a bug possibly from a tired dev with weary eyes).

Update for question edit with more specifics

Here is the same concept applied to your code. Instead of using a hidden pageBlock to utilize the rerender attribute (thus allowing apex:params to controller) it re-renders a script block, then oncompletes that function after it re-renders it (with the populated data because the button now also calls the action in the controller).

<apex:commandButton value="{!button}" action="{!calcBDialogParameters}" oncomplete="openBDialog();" rerender="bDialogScript" styleClass="btn btn-primary nbtn" >
    <!-- Allows for retrieval of SAN for a given row where a button is pressed -->
    <apex:param name="rowIndex" assignTo="{!rowCount}" value="{!rowCount}"/>
    <!-- The type of button that was clicked (e.g. Redeem) -->
    <apex:param name="buttonClicked" assignTo="{!buttonClicked}" value="{!button}"/>
</apex:commandButton>


<apex:outputPanel id="bDialogScript">
    <script type="text/javascript">
        function openBDialog() {
            var recordName="{!buttonClicked}"; <!-- retrieve value from Apex controller -->
            var parameters="{!bDialogParameters}"; <!-- retrieve value from Apex controller -->
            window.alert(recordName); <!-- Test to see what recordName is currently set to. Always shows up empty -->
            window.alert(parameters); <!-- Test to see what parameters is currently set to. Always shows up empty -->

            // timeout of 6 seconds probably not necessary here
            setTimeout(function(){BIGIANT_DIALOG.init(this, "Redeem", parameters, "{!$Api.Session_ID}")}, 6000); <!-- Opens a Modal dialog -->
        }
    </script>
</apex:outputPanel>
5
  • Thank you very much for the reply. I have used the code from the "Update for question edit with more specifics" section of your response. It still doesn't seem to work. I originally had tried something similar without the rerender of the output panel which includes the script. The values still are not populated. Any idea as to why this might be?
    – Kevin
    Jun 30, 2016 at 4:35
  • @Kevin - I did some tinkering and it seems to work, but I altered the design slightly because calling a separate function (openBDialog) was causing some trouble with the clickjack settings (although that doesn't seem by design, rather something dev console might be causing, it happened on load in my dev org too). Here is a gist with both test controller and page.
    – drakored
    Jun 30, 2016 at 6:23
  • gist.github.com/drakored/…
    – drakored
    Jun 30, 2016 at 6:24
  • In the demo I also expose some weird properties of css and let you see the script code directly in there. The majority of that code is now in the button though.
    – drakored
    Jun 30, 2016 at 6:26
  • I'm sure you are very busy, but you are obviously very intelligent. Is there a way I can show you my code and you can possibly let me know what is going on? Parameters still aren't being rendered yet.
    – Kevin
    Jun 30, 2016 at 12:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .