3

I wrote a VisualForce page that has two drop-down lists. When the user selects an item from the first list, it makes an Ajax call to populate the second list. Everything worked great.

I split the <apex:pageBlock> into its own component, and now the Ajax call shows an error after making a selection in the first list:

Insufficient Privileges

You do not have the level of access necessary to perform the operation you requested. Please contact the owner of the record or your administrator if access is necessary.

The thing is, I am an administrator. This worked fine all in one VisualForce page, but now that it is split into a separate component, it fails. The debug log shows no failures. It does not tell me the specific object with the permission error. Based on some debug logging that I added, it appears to be failing before it even enters any Apex code. I think the failure is somewhere in the Ajax call but unfortunately none of the logging or error reporting tells me anything useful.

Unfortunately I cannot share the actual code/VF that causes this because it is proprietary to the customer, but here is a SSCCE:

VisualForce:

<apex:page id="pageId" controller="MyController">
  <apex:form id="mainFormId">
    <apex:sectionHeader subtitle="Custom Lookup Demo" title="Custom Lookup"/>
    <c:MyComponent />
  </apex:form>
</apex:page>

VisualForce Component:

<apex:component controller="MyController">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/JavaScript" />
  <apex:pageBlock id="pgBId">
    <apex:outputPanel id="SearchPanelId">
      <table class="list" border="0" cellspacing="0" cellpadding="0" style="width: 44% !important">
        <tr>
          <td style="white-space:nowrap;">
            <apex:selectList size="1" value="{!selectedObject}" id="selectedObjectId">
              <apex:selectOptions value="{!SObjects}"/>
              <apex:actionSupport event="onchange" action="{!populateFields}" rerender="selectfield,SearchPanelId" status="ajaxStatus"/>
            </apex:selectList>
            <apex:actionStatus id="ajaxStatus">
              <apex:facet name="start">&nbsp;<apex:image value="{!$MyImage}" height="16" width="16" /></apex:facet>
            </apex:actionStatus>
          </td>
          <td>
            <apex:selectList size="1" value="{!selectedField}" id="selectfield">
              <apex:selectOption itemValue="" itemLabel="--Select--"/>
              <apex:selectOptions value="{!fieldsForSelectedObject}"/>
            </apex:selectList>
          </td>
        </tr>
      </table>
    </apex:outputPanel>
  </apex:pageBlock>
</apex:component>

Controller

public with sharing class MyController {

  private static final String SELECT_OBJECT = '--Select Object--';

  public String selectedObject {get; set;}
  public String selectedField {get; set;} 
  public String selectedValue {get; set;}
  public List<SelectOption> allFieldsForSelectedObject {get; set;}

  private Map<String, Schema.SObjectType> schemaCache {
    get {
      if (schemaCache == null) {
        schemaCache = Schema.getGlobalDescribe();
      }
      return schemaCache;
    }
    private set{}
  }

  public List<SelectOption> sObjects {
    get {
      System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Entering getSObjects()');
      if (sObjects == null) {
        System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Populating sobjects');
        List<SelectOption> objects = new List<SelectOption>();
        objects.add(new SelectOption('', SELECT_OBJECT));
        for (String objectKey : schemaCache.keySet()) {
          Schema.SObjectType sObjectType = schemaCache.get(objectKey);
          Schema.DescribeSObjectResult describe = sObjectType.getDescribe();
          /* If the current user cannot query the object, do not bother displaying it in the list. */
          if (describe.isQueryable()) {
            objects.add(new SelectOption(describe.getName(), describe.getLabel()));
          }
        }
        objects.sort();
        sObjects = objects;
      }
      return sObjects;
    }
    private set{}
  }

  public MyController() {
    populateFields();
  }

  /** Get the fields which will appear in the list of fields which can be queried. */
  public List<SelectOption> getFieldsForSelectedObject() {
    System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Entering getFieldsForSelectedObject()');
    List<SelectOption> fieldNames = new List<SelectOption>();
    if ((selectedObject != null) && (selectedObject != '') && (selectedObject != SELECT_OBJECT)) {

      // *************************************************************
      // This IF block NEVER executes
      // *************************************************************

      System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Entering main IF block');
      Map<String, Schema.SObjectField> fieldMap = schemaCache.get(selectedObject).getDescribe().fields.getMap();
      for (Schema.SObjectField field : fieldMap.values()) {
        Schema.DescribeFieldResult describe = field.getDescribe();
        System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Checking field ' + describe.getName());
        /* Only include this field if the current user can access it AND it can be used in a WHERE clause, otherwise the
         * search function will break. */
        if (describe.isAccessible() && describe.isFilterable()) {
          System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Adding field ' + describe.getName());
          fieldNames.add(new SelectOption(describe.getName(), describe.getLabel()));
        }
      }
    }
    return fieldNames;
  }

  /** Repopulate the fields. */
  public void populateFields() {
    System.debug('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  Entering populateFields()');
    allFieldsForSelectedObject = getFieldsForSelectedObject();
  }

}

I found this related question that was similar but not applicable to this scenario. This is a dev sandbox using Unlimited Edition.

I compared the HTML from the component being embedded using the component tag and with it directly in the VF, and the only differences are there is one more layer of DIVs and one more layer of IDs in the generated JavaScript code. Seems to look okay.

If I copy and paste the component body into the VF page, I get two boxes and they both work. Then if I do nothing other than comment out the raw component markup in the VF, it stops working.

I wonder if maybe this has something to do with the JavaScript initialization? Maybe as a component some initialization logic is not running or not running correctly, and that causes the Ajax call not to work correctly which results in the permissions error?

This is bizarre.

1
  • We escalated to Salesforce support but do not have an answer yet. It appears to be some strange issue with the generated JavaScript on the page. I was able to resolve it by renaming some classes, but that does not explain why it occurred in the first place.
    – user6861
    Jun 5, 2014 at 15:04

1 Answer 1

1

The root cause was the versions of the controllers did not match the pages. I set everything to 30.0 (Spring '14 is the current release as of this question) and it works fine.

The reason why this broke things is something in the JavaScript and Ajax behind the scenes changed between releases in an incompatible way. When submitting the request to populate the list, the server received an incompatible message which caused a 500 server error. I still have no idea where the permission error came from, maybe there is no error message for this and the system picked one?

You must log in to answer this question.