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.
- 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
- 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?
- 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;
}
}