I've written an autocomplete component using remoting which I use in a visualforce page with a component that is embedded in a standard page layout.
When I change the autocomplete component, the autocomplete works just once. If I then do a refresh of the page I get a javascript error: ReferenceError: AutocompleteController is not defined
So it looks like Salesforce sees the page the first time and creates the function to do the autocomplete remoting. The second time around this javascript seems to be cached and then salesforce doesn't create the function for the remoting.
Can anyone suggest a course of action?
It looks like this: the page that is embedded in the standard page layout of the placement:
<apex:page standardcontroller="Placement__c" cache="false">
<c:RequiredCertificatesCheckList CparentObjectId="{!Placement__c.id}" />
</apex:page>
This is the component:
<apex:component controller="RequiredCertificatesCheckList" allowDML="true">
<apex:attribute name="CparentObjectId" type="String" assignTo="{!parentObjectId}" description="The id of the parentObject" required="true"/>
<apex:form id="theForm">
<apex:outputPanel id="thePageToRefresh">
<apex:pageblock title="New Required Certificate" >
<apex:pagemessages />
<apex:pageblocksection columns="1">
<apex:pageblocksectionitem >
<apex:outputLabel value="{!$ObjectType.Required_Certificate__c.Label}" />
<apex:InputField value="{!newRC.Required_Certificate__c}" id="acc_auto" >
<c:SingleFieldAutocomplete objectname="Property__c" autocomplete_textbox="{!$Component.acc_auto}" additional_filter="Type__c='Certificates'" FieldsToDisplay="Name,Category__r.Name" FieldsToQuery="Name,Category__r.Name" limit="5"/>
</apex:InputField>
</apex:pageblocksectionitem>
</apex:pageblocksection>
<apex:pageBlockButtons location="bottom" >
<apex:commandbutton action="{!saveNewRequiredCertificate}" value="{!$Label.Save}" rerender="thePageToRefresh" />
<apex:commandbutton action="{!CancelNewRequiredCertificate}" value="{!$Label.Cancel}" rerender="thePageToRefresh" immediate="true"/>
</apex:pageBlockButtons>
</apex:pageblock>
</apex:outputPanel>
then I have the autocomplete component:
<apex:component controller="AutocompleteController" >
<apex:includeScript value="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"/>
<apex:includeScript value="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.min.js"/>
<apex:stylesheet value="//ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css"/>
<style>
.autoCompleteBoxScrolling {display:none !important;}
.ui-autocomplete{font-size:12px;}
.ui-autocomplete-loading { background: white url({!$Resource.circleIndicator}) right center no-repeat; }
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus {border:0px solid #000 !important;}
</style>
<apex:attribute name="objectname" description="The object name you want to look for." type="String" required="true"/>
<apex:attribute name="autocomplete_textbox" description="The ID for the Autocomplete List Textbox." type="String" required="true"/>
<apex:attribute name="FieldsToDisplay" description="Fields to show. comma separated" type="String" required="true"/>
<apex:attribute name="FieldsToQuery" description="Fields to query. comma separated" type="String" required="true"/>
<apex:attribute name="limit" description="Number of results to return" type="String" required="true"/>
<apex:attribute name="additional_filter" description="Additional filter to use for query" type="String" required="false"/>
<script type="text/javascript">
var q$ = jQuery.noConflict();
q$(document).ready(function() {
AutoCompleteInputElement = function(){};
q$(esc('{!autocomplete_textbox}')).attr('onchange','');
var sObjects;
var queryTerm;
q$(esc('{!autocomplete_textbox}')).autocomplete({
minLength: 1,
source: function(request, response) {
queryTerm = request.term;
AutocompleteController.findSObjectsSingle("{!objectname}","{!additional_filter}","{!FieldsToDisplay}", "{!FieldsToQuery}","{!limit}", request.term, function(result, event){
if(event.type == 'exception') {
alert(event.message);
} else {
sObjects = result;
response(sObjects);
}
});
},
focus: function( event, ui ) {
//q$(esc('{!autocomplete_textbox}')).val( ui.item.Name );
return false;
},
select: function( event, ui ) {
q$(esc('{!autocomplete_textbox}')).val( HtmlDecode(ui.item.Name ));
q$(esc('{!autocomplete_textbox}_lkid')).val( ui.item.Id );
q$(esc('{!autocomplete_textbox}_lkold')).val( ui.item.Name );
q$(esc('{!autocomplete_textbox}_mod')).val( '1' );
return false;
}
})
.data( "uiAutocomplete" )._renderItem = function( ul, item ) {
var fields_to_display = '{!FieldsToDisplay}'.split(',');
var display_items=[];
for (i in fields_to_display) {
if ((i/2)*2==i) {
fieldBreakDown = fields_to_display[i].split('.');
tempItem=item;
for (j in fieldBreakDown) {
if ((j/2)*2==j) {
tempItem=tempItem[fieldBreakDown[j]];
}
}
display_items.push(tempItem);
}
}
var display_item = display_items.join(' - ');
var temp = queryTerm.replace('&','\&\a\m\p\;');
var entry = "<a>" + display_item.replace( new RegExp( "(" + temp + ")" , 'gi' ), '<b>$1</b>' );
entry = entry + "</a>";
return q$( "<li></li>" )
.data( "item.autocomplete", item )
.append( entry )
.appendTo( ul );
};
});
function esc(myid) {
return '#' + myid.replace(/:/g,'\\:');
}
function HtmlDecode(s) {
var out = "";
if (s==null) return;
var l = s.length;
for (var i=0; i<l; i++) {
var ch = s.charAt(i);
if (ch == '&') {
var semicolonIndex = s.indexOf(';', i+1);
if (semicolonIndex > 0) {
var entity = s.substring(i + 1, semicolonIndex);
if (entity.length > 1 && entity.charAt(0) == '#') {
if (entity.charAt(1) == 'x' || entity.charAt(1) == 'X')
ch = String.fromCharCode(eval('0'+entity.substring(1)));
else
ch = String.fromCharCode(eval(entity.substring(1)));
} else {
switch (entity) {
case 'quot': ch = String.fromCharCode(0x0022); break;
case 'amp': ch = String.fromCharCode(0x0026); break;
case 'lt': ch = String.fromCharCode(0x003c); break;
........
case 'clubs': ch = String.fromCharCode(0x2663); break;
case 'hearts': ch = String.fromCharCode(0x2665); break;
case 'diams': ch = String.fromCharCode(0x2666); break;
default: ch = ''; break;
}
}
i = semicolonIndex;
}
}
out += ch;
}
return out;
}
</script>
</apex:component>
and the controller:
global with sharing class AutocompleteController {
global AutoCompleteController() {}
global AutoCompleteController(ApexPages.StandardController controller) {}
@RemoteAction
global static SObject[] findSObjectsSingle(string objectToQuery, String filterToUse,String FieldsToDisplay,String FieldsToQuery, String limitTo, string qry) {
String soql;
// check to see if the object passed is valid
Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
Schema.SObjectType sot = gd.get(objectToQuery);
if (sot == null) {
// Object name not valid
return null;
}
set<String> displayfields = new set<String>();
set<String> queryfields = new set<String>();
// fields to display
list<String> addFieldsDisp = FieldsToDisplay.split(',');
for (String af:addFieldsDisp) {
displayfields.add(af);
}
list<String> displayFieldsList = new list<String>();
for (String df:displayfields) {displayFieldsList.add(df); }
soql = 'select ' + String.join(displayFieldsList,',');
soql += ' FROM ' + objectToQuery;
// fields to query
list<String> addFieldsQuery = FieldsToQuery.split(',');
for (String af:addFieldsQuery) {
queryfields.add(af);
}
list<String> queryFieldsList = new list<String>();
for (String df:addFieldsQuery) {queryFieldsList.add(df+' like \'%' + String.escapeSingleQuotes(qry) + '%\''); }
soql += ' WHERE (' + String.join(queryFieldsList,' OR ') +')';
soql += ' AND ' + filterToUse;
soql += ' limit '+limitTo;
List<sObject> L = new List<sObject>();
try {
L = Database.query(soql);
}
catch (QueryException e) { return null; }
return L;
}
}
rerender
attributes and allow the page to do a full postback, rather than a partial page refresh, does it operate properly?