1

I have an action of type Lightning Component in which I use a component for handling lookup input field (here's link for this component: https://github.com/enreeco/inputlookup). When I open my action immediately error appears: rerender threw an error in 'markup://c:InputLookup' [Access denied: {"Go":{"namespace":"c"}}]. What may cause the problem here?

  • Did you tried to disable LockerService? Lightning Actions is a new feature in Winter 17 and LockerService is not in GA state yet so I assume there can be some issues still... – steals Oct 27 '16 at 19:45
1

This is an alternative InputLookup that I wrote which doesn't require Typeahead & seems to still work when the original InputLookup doesn't:-

Component:-

<aura:attribute name="value" type="String" description="Source / Destination value" />
<aura:attribute name="type" type="string" description="Name of the sObject from which the Id will be returned"/> 
<aura:attribute name="idFieldName" type="string" default="Id" description="The name of the Id field that should be returned. Defaults to Id but could be set to accountId for example"/> 
<aura:attribute name="searchFields" type="string" default="Name" description="Comma separated list of fields that will be searched.  Can contain related objects e.g. FirstName,Account.Name"/> 
<aura:attribute name="additionalWhereClause" type="string" default="" description="Any additional static conditions that need to be included in the where clause"/>
<aura:attribute name="sortFields" type="string" default="" description="Any sort fields"/>
<aura:attribute name="limitValue" type="string" default="20"  description="The maximum number of rows to return"/>
<aura:attribute name="returnFormatStringDisplay" type="String" default="{Name}" description="Character string that designates which fields should be shown after a match has been selected. Fields should be between fieldStartToken and fieldEndToken characters." />
<aura:attribute name="fieldStartToken" type="String" default="{" description="Character string that designates the start of a field name in returnFormatString" />
<aura:attribute name="fieldEndToken" type="String" default="}" description="Character string that designates the end of a field name in returnFormatString" />
<aura:attribute name="returnFormatStringMatches" type="String" default="{Name}" description="Character string that designates which fields should be shown in the search results. Fields should be between fieldStartToken and fieldEndToken characters." />
<aura:attribute name="excludeIds" type="Id[]" description="Array of ids that should be excluded from the search results"/>
<aura:attribute name="matches" type="InputLookupAuraController.SearchResult[]" description="matched ids and values"/>
<aura:attribute name="ObjectLabel" type="String" description="The SF Label for the object being searched.  Will get fro SF if not passed in."/>

<aura:attribute name="searchString" type="String" description="matched ids and values"/>

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

<div class="slds">
    <div class="slds-form-element">
        <div class="slds-form-element--control slds-input-has-icon slds-input-has-icon--right">
            <div aura:id="search-pill">
                <c:svg class="slds-input__icon" xlinkHref="/resource/LDS_0_12_1/assets/icons/utility-sprite/svg/symbols.svg#search"/>
            </div>
            <div aura:id="clear-pill" class="slds-hide">
                <span  onclick="{!c.ClearSearch}">                     
                    <c:svg class="slds-input__icon" xlinkHref="/resource/LDS_0_12_1/assets/icons/utility-sprite/svg/symbols.svg#close"/>
                </span>
            </div>
            <!-- This markup is for when searching for a string -->
            <ui:inputText aura:id="lookup" value="{!v.searchString}" class="slds-input" updateOn="keyup" keyup="{!c.Search}" />
        </div>
    </div>
    <!-- This is the lookup list markup. Initially it's hidden -->

    <div aura:id="lookupList" class="slds-hide" role="listbox">
        <div class="slds-lookup--item">
            <button class="slds-button">
                <c:svg class="slds-icon slds-icon-text-default slds-icon--small" xlinkHref="/resource/LDS_0_12_1/assets/icons/utility-sprite/svg/symbols.svg#search" />
                &apos;{!v.searchString}&apos; in {!v.ObjectLabel}
            </button>   
        </div>            
        <ul aura:id="lookuplist-items" class="slds-lookup--list" role="presentation">
            <aura:iteration items="{!v.matches}" var="match">
                <li class="slds-lookup--item">
                    <a id="{!match.id}" role="option" onclick="{!c.AddMatch}">
                        <c:svg class="{!'slds-icon ' + ' slds-icon--small'}"/>{!match.matchName}
                    </a>
                </li>
            </aura:iteration>
        </ul>
    </div>
</div>

Controller :-

({
doInit :function(component, event, helper) {
    helper.doInit(component);
},
Search :function(component, event, helper) {
    helper.doSearch(component);
},
AddMatch :function(component, event, helper) {
    component.set("v.value", event.currentTarget.id);

    var matches = component.get("v.matches");
    for(var i=0; i<matches.length; i++) {
        if (matches[i].id ==event.currentTarget.id){
            component.set("v.searchString", matches[i].chosenName);
            continue;
        }
    }

    // Hide the Lookup List
    var lookupList = component.find("lookupList");
    $A.util.addClass(lookupList, 'slds-hide');

},
ClearSearch :function(component, event, helper) {
    var lookupList = component.find('lookupList');
    var clearPill = component.find('clear-pill');
    var searchPill = component.find('search-pill');

    // Hide the lookuplist
    $A.util.addClass(lookupList, 'slds-hide');
    $A.util.addClass(clearPill, 'slds-hide');        
    $A.util.removeClass(searchPill, 'slds-hide');
    component.set('v.searchString', null);
    component.set('v.value', null);

}
})

Helper:-

({
/**
 * Perform the SObject search via an Apex Controller
 */
doSearch : function(component) {
    // Get the search string, input element and the selection container
    var searchString = component.get('v.searchString');
    var lookupList = component.find('lookupList');
    var clearPill = component.find('clear-pill');
    var searchPill = component.find('search-pill');

    // We need at least 2 characters for an effective search
    if (typeof searchString === 'undefined' || searchString.length < 2)
    {
        // Hide the lookuplist
        $A.util.addClass(lookupList, 'slds-hide');
        $A.util.addClass(clearPill, 'slds-hide');        
        $A.util.removeClass(searchPill, 'slds-hide');
        return;
    }

    // Show the lookuplist
    $A.util.removeClass(lookupList, 'slds-hide');        
    $A.util.removeClass(clearPill, 'slds-hide');        
    $A.util.addClass(searchPill, 'slds-hide');

    // Create an Apex action
    var action = component.get('c.getResults');

    // Mark the action as abortable, this is to prevent multiple events from the keyup executing
    action.setAbortable();

    // Set the parameters
    action.setParams({
        'objectName' : component.get('v.type'),
        'idFieldName' : component.get('v.idFieldName'),
        'searchFields' : component.get('v.searchFields'),
        'sortFields' : component.get('v.sortFields'),
        'searchValue' : searchString,
        'additionalWhereClause' : component.get('v.additionalWhereClause'),
        'limitValue' : component.get('v.limitValue'),
        'returnFormatStringDisplay' : component.get('v.returnFormatStringDisplay'),
        'fieldStartToken' : component.get('v.fieldStartToken'),            
        'fieldEndToken' : component.get('v.fieldEndToken'),
        'returnFormatStringMatches' : component.get('v.returnFormatStringMatches'),
        'excludeIds' : component.get('v.excludeIds')
    });

    // Define the callback
    action.setCallback(this, function(response) {
        var state = response.getState();
        // Callback succeeded
        if (component.isValid() && state === "SUCCESS")
        {
            // Get the search matches
            var result = response.getReturnValue();

            // an array that will be populated with substring matches
            var matches = [];

            var matches = JSON.parse(result);

            component.set('v.matches', matches);

        }
        else if (state === "ERROR") // Handle any error by reporting it
        {
            var errors = response.getError();

            if (errors) 
            {
                if (errors[0] && errors[0].message) 
                {
                    alert(errors[0].message);
                }
            }
            else
            {
                Alert('Unknown error.');
            }
        }
    });

    // Enqueue the action                  
    $A.enqueueAction(action);                
},
doInit : function(component) {

    if (component.get("v.ObjectLabel") == null){
        // Create an Apex action
        var action = component.get('c.getObjectSearchLabel');
        action.setParams({
            'objectName' : component.get('v.type')
        });

        // Define the callback
        action.setCallback(this, function(response) {
            var state = response.getState();
            // Callback succeeded
            if (component.isValid() && state === "SUCCESS")
            {
                // Get the label of the object
                var result = response.getReturnValue();

                component.set('v.ObjectLabel', result);

            }
            else if (state === "ERROR") // Handle any error by reporting it
            {
                var errors = response.getError();

                if (errors) 
                {
                    if (errors[0] && errors[0].message) 
                    {
                        alert(errors[0].message);
                    }
                }
                else
                {
                    Alert('Unknown error.');
                }
            }
        });

        // Enqueue the action                  
        $A.enqueueAction(action); 
    }

    var actionV = component.get("c.getCurrentValue");
    var self = this;
    actionV.setParams({
        'type' : component.get('v.type'),
        'value' : component.get('v.value'),
    });

    actionV.setCallback(this, function(a) {
        if(a.error && a.error.length){
            return $A.error('Unexpected error: '+a.error[0].message);
        }
        var result = a.getReturnValue();
        component.set('v.searchString',result || '');
    });
    $A.enqueueAction(actionV);
}
})

Apex controller :-

public with sharing class InputLookupAuraController {
@AuraEnabled
public static String getObjectSearchLabel(String objectName) 
{
    list<schema.describeSObjectResult> dsors = Schema.describeSObjects(new List<String> {objectName});
    if (dsors==null || dsors.size()==0){
        return objectName;
    } 
    return dsors[0].getLabel();
}

/*
 * Use SQL to return JSON serialized string containing the id & formatted string
 * of results based upon the user's selection criteria
*/
@AuraEnabled
public static String getResults(String objectName,
                                   String idFieldName, 
                                   String searchFields, 
                                   String sortFields, 
                                   String searchValue,
                                   String additionalWhereClause,
                                   String limitValue,
                                   String returnFormatStringDisplay,
                                   String fieldStartToken,
                                   String fieldEndToken,
                                   String returnFormatStringMatches,
                                   list<Id> excludeIds
                                   ) 
{
    if (idFieldName == null || idFieldName == ''){
        idFieldName = 'Id';
    }

    if (searchFields == null || searchFields == ''){
        searchFields = 'Name';
    }

    if (limitValue == null || limitValue == ''){
        limitValue = '50';
    }

    if (fieldStartToken == null || fieldStartToken == ''){
        fieldStartToken = '{';
    }

    if (fieldEndToken == null || fieldEndToken == ''){
        fieldEndToken = '}';
    }

    if (returnFormatStringDisplay == null || returnFormatStringDisplay == ''){
        returnFormatStringDisplay = fieldStartToken + 'Name' + fieldEndToken;
    }

    if (returnFormatStringMatches == null || returnFormatStringMatches == ''){
        returnFormatStringMatches = returnFormatStringDisplay;
    }

    // soqlFields contains a set of fields that are required to be returned by the soql        
    set<string> soqlFields = new set<string>(); 
    soqlFields.add(idFieldName.toLowerCase());  // Id is always returned

    // Split the search fields by comma & add to the list of required fields.
    // Also build up the search clause
    string[] searchFieldsArr = searchFields.split(',');
    String searchFieldsString = '';
    for(string str : searchFieldsArr){
        searchFieldsString = searchFieldsString + (searchFieldsString!=''?' or ':'') + str + ' like \'' + searchValue + '%\'';
        soqlFields.add(str.trim().toLowerCase());
    }

    // Deconstruct returnFormatStringMatches, looking for fields that need to be replaced by their values
    //Set<String> requiredFields = getRequiredFields(returnFormatStringMatches, fieldStartToken, fieldEndToken);
    Set<String> requiredFields = new Set<String>();
    integer posStart = returnFormatStringMatches.indexOf(fieldStartToken);
    integer posEnd = -1;
    while (posStart > -1){
        posEnd = returnFormatStringMatches.indexOf(fieldEndToken, posStart+1);
        if (posEnd > -1){
            requiredFields.add(returnFormatStringMatches.substring(posStart+1,posEnd).toLowerCase());
            returnFormatStringMatches = (posStart > 0?returnFormatStringMatches.substring(0,posStart):'') + returnFormatStringMatches.substring(posStart,posEnd+1).toLowerCase() + (posEnd<returnFormatStringMatches.length()?returnFormatStringMatches.substring(posEnd+1):'');
            posStart = returnFormatStringMatches.indexOf(fieldStartToken, posEnd+1);
        } else {
            posStart = -1;
        }
    }

    posStart = returnFormatStringDisplay.indexOf(fieldStartToken);
    posEnd = -1;
    while (posStart > -1){
        posEnd = returnFormatStringDisplay.indexOf(fieldEndToken, posStart+1);
        if (posEnd > -1){
            requiredFields.add(returnFormatStringDisplay.substring(posStart+1,posEnd).toLowerCase());
            returnFormatStringDisplay = (posStart > 0?returnFormatStringDisplay.substring(0,posStart):'') + returnFormatStringDisplay.substring(posStart,posEnd+1).toLowerCase() + (posEnd<returnFormatStringDisplay.length()?returnFormatStringDisplay.substring(posEnd+1):'');
            posStart = returnFormatStringDisplay.indexOf(fieldStartToken, posEnd+1);
        } else {
            posStart = -1;
        }
    }

    soqlFields.addAll(requiredFields);


    // Build up the SOQL
    String soql = '';
    for(String str : soqlFields) {
        if (str != ''){
            soql = (soql==''?'select ' + str:soql + ', ' + str); 
        }                                       
    }                                  
    soql = soql + ' from ' + objectName + ' where (' + searchFieldsString + ')';
    if (additionalWhereClause != null && additionalWhereClause != ''){
         soql = soql + ' AND (' + additionalWhereClause + ')';                                 
    }
    if (sortFields != null && sortFields != '') {                                 
        soql = soql + ' order by ' + sortFields;
    }
    soql = soql + ' limit ' + limitValue;

    // Return tne SOQL results
    list<sObject> results = Database.query(soql);

    // Convert to Map to allow removal of exclude ids
    map<id, sObject> resultsMap = new map<id, sObject>();
    resultsMap.putAll(results);


    if (excludeIds != null){
        for(id excludeId : excludeIds){
            system.debug('checking:' + excludeId);
            if (resultsMap.containsKey(excludeId)){
                system.debug('removing:' + excludeId);
                resultsMap.remove(excludeId);
            }
        }   
    }


    List<SearchResult> output = new List<SearchResult>();

    for(SObject sobj : resultsMap.values()){
        SearchResult sr = new SearchResult();
        sr.id = InputLookupAuraController.returnFieldValue(sobj, idFieldName.toLowerCase());
        sr.matchName = returnFormatStringMatches;
        for (String field : requiredFields){
            sr.matchName=sr.matchName.replace(fieldStartToken + field.toLowerCase() + fieldEndToken, InputLookupAuraController.returnFieldValue(sobj, field));
        }

        sr.chosenName = returnFormatStringDisplay;
        for (String field : requiredFields){
            sr.chosenName=sr.chosenName.replace(fieldStartToken + field.toLowerCase() + fieldEndToken, InputLookupAuraController.returnFieldValue(sobj, field));
        }
        output.add(sr);

    }

    return JSON.serialize(output);   
    //return output;                                

}

/*
 * Return the field value from sObj
 * This would normally be done with a .get e.g. sObj.get(field) but is complicated
 * by NOT being able to refer to related objects using that notation e.g. sObj.get('account.name') doesn't work.
 * Need to drill down through objects first.
*/
private static string returnFieldValue(sObject sObj, string field){
    string returnValue = '';
    string[] subfieldsArr = field.split('\\.');
    sObject tempObj = sObj;
    integer count = 0;               

    for(string str : subfieldsArr){
        if (tempObj != null){
            count++;
            if (count == subfieldsArr.size()){
                if(tempObj.get(str) != NULL){
                    returnValue=(String) tempObj.get(str);
                } 

            } else {
                if(tempObj.getSobject(str) != NULL){
                    tempObj = tempObj.getSobject(str);
                } else {
                    tempObj = null;
                }
            }
        }

    }
    return returnValue;
}

@AuraEnabled
public static String getCurrentValue(String type, String value){
    if(String.isBlank(type)){
        return null;
    }

    ID lookupId = null;
    try{   
        lookupId = (ID)value;
    }catch(Exception e){
        return null;
    }

    if(String.isBlank(lookupId)) return null;

    SObjectType objType = Schema.getGlobalDescribe().get(type);
    if(objType == null) return null;

    String nameField = getSobjectNameField(objType);
    String query ='Select Id, '+nameField+' From '+type+' Where Id = \''+lookupId+'\'';

    List<SObject> oList = Database.query(query);
    if(oList.size()==0) return null;

    return (String) oList[0].get(nameField);
}

/*
 * Returns the "Name" field for a given SObject (e.g. Case has CaseNumber, Account has Name)
*/
private static String getSobjectNameField(SobjectType sobjType){

    //describes lookup obj and gets its name field
    String nameField = 'Name';
    Schema.DescribeSObjectResult dfrLkp = sobjType.getDescribe();
    for(schema.SObjectField sotype : dfrLkp.fields.getMap().values()){
        Schema.DescribeFieldResult fieldDescObj = sotype.getDescribe();
        if(fieldDescObj.isNameField() ){
            nameField = fieldDescObj.getName();
            break;
        }
    }
    return nameField;
}

/*
 * Utility class for search results
*/
public class SearchResult{
    public String matchName{get;Set;}
    public String chosenName{get;Set;}
    public String id{get;set;}
}
}

The 'LDS_0_12_1' references in the component controller will need to be altered to be your Lightning Design System resource name.

Example usage:-

Search contacts that have a status of Active & have a first name starting with the search value or work for an account whose name starts with the search value. Results will show the first & last name + the name of the account. When one is selected only the first name & last name will be displayed. The chosen contact's id will be returned & placed in contactId.

I know it is not solving your problem but might provide a workaround

| improve this answer | |
  • Yeah, it works. I'm still curious how to resolve the issue from my question. Thanks for sharing it! – rmatusiak Oct 27 '16 at 15:06
1

We are a lot to have the same bug since the framework update of few days ago. (as usual... i can't remember the last Lightning framework update who didn't break anything )

I read in Partner community that this is a bug and that it will be solved soon.

Meanwhile, unfortunately there is no other choice than deactivating LockerService :/

| improve this answer | |
0

I know it's not an answer but are you only seeing this problem in sandboxes? We've suddenly started getting it too but still working in production orgs. Just thought it might help narrow the issue down.

| improve this answer | |
  • The problem occurs in sandboxes. I don't have this feature on prod yet so I can't check. The thing is that Salesforce firstly updates sandboxes and then prods. If this issue is connected with some Salesforce update... – rmatusiak Oct 27 '16 at 11:39
0

I disabled LockerService and it helped me in the same situation.

| improve this answer | |
  • Not a viable long term solution. – Adrian Larson Oct 28 '16 at 13:31
  • @AdrianLarson While it may not be a long term solution, until Salesforce finishes getting their security issues sorted out with Lightning and we have a fully stable platform, there are going to be situations where disabling the LockerService may be the only viable solution. A code fix that works today could break again tomorrow with the next patch. – crmprogdev Oct 29 '16 at 15:26

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.