I have the following code that I adapted from a response to another question on Stackoverflow. I am very appreciative of the answer provided by @eyescream.
I am now trying to display a link to account objects that has come back from a JavaScript Remoting call to an Apex method. I get the name and ID, but not the URL I am passing back.
Here is the page:
<apex:page docType="html-5.0" controller="Stack73082136">
<h1>Account Type-Ahead Search Demo VF</h1>
<apex:form >
<script>
function callRemote(term){
// call static method in ClassName.methodName format
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.Stack73082136.getMatchingAccounts}',
term,
false, // no contacts pls
function(result, event){
if (event.status) {
//debugger;
let target = document.getElementById("out3");
while (target.firstChild) {
target.removeChild(target.firstChild);
}
result.forEach((item) => { console.log(item.name); console.log(item.link); target.append('<A href="' + item.link + '">' + item.name + '|' + item.link + '</A>' ) } );
} else if (event.type === 'exception') {
document.getElementById("responseErrors").innerHTML =
event.message + "<br/>\n<pre>" + event.where + "</pre>";
} else {
document.getElementById("responseErrors").innerHTML = event.message;
}
},
{escape: true}
);
}
</script>
<apex:pageBlock title="3. VF remoting, the grandfather of Aura. No viewstate, pure html and js">
<input type="text" id="text3" label="type and vait v2" onkeyup="callRemote(this.value)" />
<div id="out3"></div>
<div id="responseErrors"></div>
</apex:pageBlock>
</apex:form>
</apex:page>
Here is the Apex:
public class Stack73082136 {
// Service part (static, useable in Aura/LWC but eventually maybe also as a REST service)
// This method would typically throw exceptions, perhaps AuraHandledException
@RemoteAction @AuraEnabled(cacheable=true)
public static MatchingAccountsWrapper[] getMatchingAccounts(String searchString, Boolean showContacts) {
String searchSpec = '%' + searchString + '%';
List<Account> accountsFound;
if (showContacts) {
accountsFound = [
SELECT Id, Name,
(SELECT Id, Name FROM Contacts ORDER BY Name)
FROM Account
WHERE Name LIKE :searchSpec
ORDER BY Name];
} else {
accountsFound = [
SELECT Id, Name
FROM Account
WHERE Name LIKE :searchSpec
ORDER BY Name];
}
List<MatchingAccountsWrapper> matchingAccounts = new List<MatchingAccountsWrapper>();
for (Account ma : accountsFound) {
MatchingAccountsWrapper mar = new MatchingAccountsWrapper(ma.Id, ma.Name, showContacts ? ma.Contacts: null);
matchingAccounts.add(mar);
system.debug('#@# matching account.name = ' + ma.Name);
system.debug('#@# matching mar.name = ' + mar.name);
system.debug('#@# matching mar.link = ' + mar.link);
}
return matchingAccounts;
}
// Visualforce part (old school, stateful)
// This would typically not throw exceptions but ApexPages.addMessage() etc.
// (which means that non-VF context like a trigger, inbound email handler or code called from Flow would crash and burn at runtime; in these you can only do exceptions)
public String searchValue {get;set;}
public List<MatchingAccountsWrapper> getMatchingAccountsVF() {
return getMatchingAccounts(searchValue, false);
}
public void onKeyUpHandlerVF() {
system.debug('#@# AccountTypeAheadSearchHelper:onKeyUpHandlerVF(): BEGIN');
system.debug('#@# AccountTypeAheadSearchHelper:onKeyUpHandlerVF(): accountSearchVFValue = ' + searchValue);
// do nothing. this method is "stupid", it's only job is to be called, pass the parameter and then the getMatchingAccountsVF
// will be called by VF engine when it needs to rerender {!matchingAccountsVF} expression
}
public class MatchingAccountsWrapper {
public MatchingAccountsWrapper(String k, String n) {
key = k;
name = n;
}
public MatchingAccountsWrapper(String k, String n, List<Contact> c) {
key = k;
name = n;
relatedContacts = c;
}
public MatchingAccountsWrapper(Account a) {
key = a.Id;
name = a.Name;
}
@AuraEnabled
public string key {get; set;}
@AuraEnabled
public string name {get; set;}
@AuraEnabled
public string link {get {
return URL.getSalesforceBaseUrl().toExternalForm() + '/' + this.key;
} set;}
private List<Contact> relatedContacts {get; set;}
@AuraEnabled
public List<MatchingContactsWrapper> contacts {get {
if (relatedContacts != null) {
List<MatchingContactsWrapper> matchingContacts = new List<MatchingContactsWrapper>();
for (Contact matchingContact : relatedContacts) {
MatchingContactsWrapper mac = new MatchingContactsWrapper(matchingContact);
matchingContacts.add(mac);
}
return matchingContacts;
} else {
return null;
}
} set;}
}
private class MatchingContactsWrapper {
public MatchingContactsWrapper(Contact c) {
key = c.Id;
name = c.Name;
}
@AuraEnabled
public string key {get; set;}
@AuraEnabled
public string name {get; set;}
@AuraEnabled
public string link {get {
return URL.getSalesforceBaseUrl().toExternalForm() + '/' + this.key;
} set;}
}
}
The Apex debug log shows the link property is populated but the JavaScript console.log does not. And this is what I get displayed on my page when I search for st:
<A href="undefined">Express Logistics and Transport|undefined</A><A href="undefined">Pyramid Construction Inc.|undefined</A>
I did find that if I set the value of link in the constructors like this, it works later to just use the simple link property:
key = k;
name = n;
link = URL.getSalesforceBaseUrl().toExternalForm() + '/' + k;
}
public MatchingAccountsWrapper(String k, String n, List<Contact> c) {
key = k;
name = n;
relatedContacts = c;
link = URL.getSalesforceBaseUrl().toExternalForm() + '/' + k;
}
// @AuraEnabled
// global string link {get {
// return URL.getSalesforceBaseUrl().toExternalForm() + '/' + this.key;
// } set;}
@AuraEnabled
global string link {get; set;}
I don't know why this is necessary, though. It works with this page code:
<apex:page docType="html-5.0" controller="Stack73082136">
<h1>Account Type-Ahead Search Demo VF</h1>
<apex:form >
<script>
function callRemote(term){
// call static method in ClassName.methodName format
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.Stack73082136.getMatchingAccounts}',
term,
false, // no contacts pls
function(result, event){
if (event.status) {
//debugger;
let target = document.getElementById("out3");
while (target.firstChild) {
target.removeChild(target.firstChild);
}
result.forEach((item) => {
console.log(item.name);
console.log(item.link);
let a = document.createElement('a');
let text = document.createTextNode(item.name);
a.appendChild(text);
a.title = item.name;
a.href = item.link;
a.target = '_blank';
target.appendChild(a);
let br = document.createElement('br');
target.appendChild(br);
} );
} else if (event.type === 'exception') {
document.getElementById("responseErrors").innerHTML =
event.message + "<br/>\n<pre>" + event.where + "</pre>";
} else {
document.getElementById("responseErrors").innerHTML = event.message;
}
},
{escape: true}
);
}
</script>
<apex:pageBlock title="3. VF remoting, the grandfather of Aura. No viewstate, pure html and js">
<input type="text" id="text3" label="type and vait v2" onkeyup="callRemote(this.value)" />
<div id="out3"></div>
<div id="responseErrors"></div>
</apex:pageBlock>
</apex:form>
</apex:page>