0

I need to embed an LWC stateful button component in the rows of VisualForce output that appear after being refreshed from an apex:actionfunction call to an Apex custom set controller. The purpose is to allow a checkbox field on a record in the list to be updated quickly and asynchronously. The button will show "Selected", "Not Selected", and "Saving..."

The LWC is provided in the standard way (wrapped in an Aura App and instantiated using $Lightning.use / $Lightning.createComponent).

The LWC embedding works when embedded in a placeholder div that is already available at the top of the page using this method, however I need to embed into placeholder divs in every row of the search results after their refresh. The apex:actionfunction's oncomplete is not calling refreshflagbuttons(). onbeforedomupdate also did not work.

I saw here that "Creating components where the top-level components don’t have server dependencies but nested inner components do have dependencies isn’t currently supported." I'm not sure if that applies here, and am not sure why the oncomplete is not firing.

I was unable to find an example of lightning components being embedded in search result rows in VisualForce. Here are the options I can think of, if oncomplete just won't work.

  1. Instead of having the Refresh button call apex directly, have it call javascript which then makes the call, and then fixes the divs. I guess like Javascript Remoting
  2. Implement a plain VF form for each button on each row. For some reason this also is not working yet for me either. Maybe because VF is treating the recordset output as part of the form and is not allowing form within a form?
  3. Give up, just make a standard VF input field. Unfortunately this means adding a Submit button to update all the records at once in the controller. (I already have a Refresh button and I think people might forget to click Submit after clicking records to select.)

Visualforce (just the relevant bits):

<apex:page controller="mycontroller" docType="html-5.0" 
    lightningStyleSheets="true" renderAs="html"
    readOnly="true">
<apex:includeLightning/>

<script>
function refreshflagbuttons() {
  console.log('refreshflagbuttons called');
  <!-- Create a meeting flag editor component for all record ids -->
  var recids = {!recordIdList};
  for (const recid of recids) {
    $Lightning.use("c:MtgFlagButtonWrapperApp", function() {
      $Lightning.createComponent("c:mtgFlagButton",{ 
          message   : 'msg-'+recid,
          recordId : recid
        },
        {
          'id' : 'flagbtn-' + recid
        }, // the Id of div tag where component will be rendered
        function(cmp) {
           console.log('Calling LWC for record ID '+recid);
        }
      ); //Lightning.createComponent
    }); //Lightning.use
  } //for

}
</script>

<head>
    <apex:slds />
</head>
<body class="slds-scope" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

<apex:form>
 ...
  <apex:actionFunction action="{!refreshButtonClicked}" name="refreshjs" 
reRender="report" oncomplete="refreshflagbuttons()"/>

  <button class="slds-button slds-button_neutral" onclick="refreshjs">Refresh</button>
</apex:form>

<apex:outputPanel id="report"     layout="block" styleClass="container">
    <apex:pageBlock>
        <apex:pageBlockTable  value="{!recs}" var="o" >
            <apex:column 
                headerValue="Selected" 
                width="10%"
                style="vertical-align:top">
                <div id="flagbtn-{!o.Id}"></div>
             </apex:column>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:outputPanel>
</body>
</apex:page>

Aura (MeetingFlagButtonWrapperApp.app):

<aura:application access="GLOBAL" extends="ltng:outApp" >
    <aura:dependency resource="c:mtgFlagButton"/>
    <c:mtgFlagButton></c:mtgFlagButton>
</aura:application>

LWC (mtgFlagButton.js) :

import { api,LightningElement } from 'lwc';

export default class MtgFlagButton extends LightningElement {
    @api buttonlabel = 'Button Label';
    cnt = 1;
    @api clickedtext = 'xxx';
    @api message;
    @api recordId;
    handleClick(event) {
        this.cnt++;
        this.buttonlabel = 'Click no. ' +this.cnt+ ' ' +this.recordId+' '+this.message;
        this.clickedtext = event.target.label;
    }
}

1 Answer 1

0

Just to follow up, I decided not to waste more time and scrapped the idea of embedding LWC in VF rows. Instead, I created SLDS buttons from scratch. It is way easier if you stop trying to figure out what VF is doing (as it is undocumented.)

Instead, I used Javascript remoting. The button's onclick calls a Javascript function that sends the record id to the apex @RemoteAction method by calling Visualforce.remoting.Manager.invokeAction(). This call specifies a result handler.

The apex method returns the record id and new state as the result. The Javascript remoting result handler unpacks the result and finds the HTML element to be updated, which is an HTML span tag: either one that contains a checkmark svg symbol or a different span containing an empty box svg symbol. Then the result handler rewrites the innerHTML of the element so that it matches the result.

I used Chrome's dev tools to see what the HTML looks like after the button is rendered in either state to figure out how it should look.

It looks like this:

Apex:
    @RemoteAction
    global static String updateDeptFlag(String recid) {
        if (!String.isBlank(recid)) {
            Contact_History__c note = [
                SELECT Id, Flag_Department__c
                FROM Contact_History__c
                WHERE Id=:recid
            ];
            String result = recid + ',notselected';
            if (note.Flag_Department__c) {
                note.Flag_Department__c = false;
            }
            else {
                note.Flag_Department__c = true;
                result = recid + ',selected';
            }
            update note;
            return result;
        }
        else {
            return 'Failed!';
        }
        
    }

VisualForce:
<script type="text/javascript">    
    function handleDeptFlagResult (result,event) {
        if(event.status) {
            const vals = result.split(',');
            const mtgid = vals[0];
            const flagstr = vals[1];

            // for single state toggle button
            // rewrite outerHTML of the flagboxspan/flagboxspan2 elements
            let sp = 'flagboxspan';
            let sp2 = sp + '2';
            let e = document.getElementById(sp + "-" + mtgid);
            if ((typeof e === 'undefined') || (e === null)) {
                e = document.getElementById(sp2 + "-" + mtgid);
            }
            if ((typeof e !== 'undefined') && (e !== null)) {
                let svg1 = '<svg aria-hidden="true" class="slds-icon slds-icon_small slds-icon-text-default"><use xlink:href="/_slds/icons/';//was slds-icon_x-small
                let svg1not = '<svg aria-hidden="true" class="slds-icon slds-icon_small slds-icon-text-default"><use xlink:href="/_slds/icons/';
                let svg2 = 'utility-sprite/svg/symbols.svg#check"></use></svg>';
                let svg2not = 'standard-sprite/svg/symbols.svg#empty"></use></svg>';
                let svg3 = ' Selected';
                let svg3not = 'Not Selected';
                if (flagstr == 'selected') {
                    e.title = 'Selected';
                    // e.innerHTML = svg1 + svg2 + svg3;
                    e.innerHTML = svg1 + svg2;
                }
                else {
                    e.title = 'Not Selected';
                    // e.innerHTML = svg1not + svg2not + svg3not;
                    e.innerHTML = svg1not + svg2not;
                }
                // console.log('CLASSNAME ' + e.className + ' ID '+e.id + ' OUTERHTML '+e.outerHTML);
            }
/*
innerHTML:
<svg aria-hidden="true" class="slds-icon slds-icon_small slds-icon-text-default">
    <use xlink:href="/_slds/icons/standard-sprite/svg/symbols.svg#empty"></use>
</svg>Not Selected
or
<svg aria-hidden="true" class="slds-icon slds-icon_x-small slds-icon-text-default">
    <use xlink:href="/_slds/icons/utility-sprite/svg/symbols.svg#check"></use>
</svg>Selected

outerHTML:
<span class="slds-icon_container slds-icon-utility-check" id="flagboxspan-a000l00000HltUcAAJ" title="Selected">
    <svg aria-hidden="true" class="slds-icon slds-icon_x-small slds-icon-text-default">
        <use xlink:href="/_slds/icons/utility-sprite/svg/symbols.svg#check"></use>
    </svg>Selected
</span>
*/

        }
    }
    function sendUpdateDeptFlag(mtgid) {
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.GlobalMtgReportController.updateDeptFlag}',
            mtgid, 
            handleDeptFlagResult
        );
    }
</script>

                 <div class="slds-grid" style="white-space: nowrap;">
                    <div class="slds-col slds-size_1-of-6" id="flagbox-{!o.Id}" style="width:30px">
                        <apex:outputPanel rendered="{!(o.Flag_Department__c = true)}">
                            <span id="flagboxspan-{!o.Id}" class="slds-icon_container slds-icon-utility-check" title="Selected" style="text-align:center;white-space: nowrap;">
                                <svg class="slds-icon slds-icon_small slds-icon-text-default" aria-hidden="true">
                                    <use xlink:href="{!URLFOR($Asset.SLDS,'/assets/icons/utility-sprite/svg/symbols.svg#check')}"></use>
                                </svg>
                            </span>
                        </apex:outputPanel>
                        <apex:outputPanel rendered="{!(o.Flag_Department__c = false)}">
                            <span id="flagboxspan2-{!o.Id}" class="slds-icon_container slds-icon-utility-check" title="Not Selected" style="text-align:center;white-space: nowrap">
                                <svg class="slds-icon slds-icon_small slds-icon-text-default" aria-hidden="true">
                                    <use xlink:href="{!URLFOR($Asset.SLDS,'/assets/icons/standard-sprite/svg/symbols.svg#empty')}"></use>
                                </svg>
                            </span>
                        </apex:outputPanel>
                    </div>
                    <div class="slds-col slds-size_5-of-6">
                        <button id="flag-{!o.Id}" class="slds-button slds-button_stateful slds-button_neutral" aria-live="assertive" aria-pressed="true" title="Edit Checkbox"
                        onclick="sendUpdateDeptFlag('{!o.Id}')"
                        >Change</button>
                    </div>
                </div>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.