4

I have a visualforce page/apex class that maps locations (using apex:map) and creates a distance matrix (callout to google). It always works perfectly the first time but sometimes when I resubmit it with different form options (checkboxes that change the query) it fails with:

"Subscript value X not valid. Must be between 0 and Y".

It doesn't seem to be a query issue because no matter what options I choose (which updates the query) it works the first time and then fails the second time if I change the options. Possibly some variables aren't getting cleared out but I'm not sure.

When you click the "Find Clinic" command button, it calls the "findLocations" method in the apex class. After the code is finished processing, the map rerenders and jquery makes an ajax call to the controller for the distMatrixCallout method. When the callout method is finished, the distance matrix is rerendered.

<apex:page controller="ProviderLocMapDistanceMatrix" docType="html-5.0"
    showHeader="false" sidebar="false" standardStylesheets="false">
    <html>
        <head>
            <META HTTP-EQUIV="Content-type" CONTENT="text/html; charset=UTF-8"/>
            <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" ></script>
            <script>        
                j$ = jQuery.noConflict();

                var offset = 0;
                var numPaged = 5;
                var numResults;
                var firstAddress;

                j$(document).ready(function(){
                    firstAddress = j$('input[type=radio][name=address]')[0];
                    j$(firstAddress).prop('checked', true);
                });

                function ref2Provider(provLocId){
                    alert('refer to provider with provider location id: '+provLocId);
                }

                function showPagedDistMatMapMarker(changeVal){
                    offset += changeVal*2;

                    if(offset < 0){
                        offset = 0;
                        return;
                    }

                    if(offset >= numResults*2){
                        offset -= changeVal*2;
                        return;
                    }

                    j$('.dist_mat_row').addClass('hidden'); // hide all
                    j$('.dist_mat_row2').addClass('hidden'); // hide all

                    // show current ones            
                    for(x = 1; x <= numPaged*2; x++){                
                        if(x % 2 == 0){
                            j$('.dist_mat_row2:nth-of-type('+(offset+x)+')').removeClass('hidden');
                        }else{
                            j$('.dist_mat_row:nth-of-type('+(offset+x)+')').removeClass('hidden');
                        }
                    }
                }
            </script>
        </head>
    <body>

        <div class="col-md-12 well filter-box">
            <apex:form >
                <div class="col-md-4">
                    <p>Services Provided</p>
                    <label><apex:inputCheckbox id="service1" value="{!service1}"/> <span>Service 1</span></label><br />
                    <label><apex:inputCheckbox id="service2" value="{!service2}"/> <span>Service 2</span></label><br />
                    <label><apex:inputCheckbox id="service3" value="{!service3}"/> <span>Service 3</span></label><br />
                    <label><apex:inputCheckbox id="service4" value="{!service4}"/> <span>Service 4</span></label>
                </div>
                <div class="col-md-4">            
                    <p>Contract Type</p>
                    <label><apex:inputCheckbox id="contract1" value="{!contract1}"/> <span>Contract 1</span></label><br />
                    <label><apex:inputCheckbox id="contract2" value="{!contract2}"/> <span>Contract 2</span></label><br />
                    <label><apex:inputCheckbox id="contract3" value="{!contract3}"/> <span>Contract 3</span></label><br />
                    <label><apex:inputCheckbox id="contract4" value="{!contract4}"/> <span>Contract 4</span></label>
                </div>
                <div class="col-md-4">            
                    <div class="right" style="margin:50px 20px 0 0;">
                        <apex:commandButton action="{!findLocations}" id="getMapBtn"
                            value="Find Clinic" reRender="mapBlock" status="mapStatus"
                            styleClass="btn btn-lrg btn-success"/>
                    </div>            
                </div>
            </apex:form>
        </div>

        <div class="col-md-12 match-my-cols">
            <div class="well col-md-6 dist-matrix">            

                <apex:pageBlock id="responseBlock">
                    <apex:actionStatus id="pageStatus">
                        <apex:facet name="start">
                            <div class="loader">Loading...</div>
                        </apex:facet>
                        <apex:facet name="stop">
                            <script>
                                var ajaxEl = j$('.ajax');
                                var dmpagerEl = j$('.dmpager');
                                numResults = {!IF(matrixAvailable,distanceMatrix.size, 0)};
                                {!IF(matrixAvailable,"ajaxEl.removeClass('hidden');dmpagerEl.removeClass('hidden');showPagedDistMatMapMarker(0);showCurrentAddress(j$(firstAddress));", "")}
                            </script>
                        </apex:facet>
                    </apex:actionStatus>

                    <section class="ajax hidden">
                        <p>Providers Near</p>
                    </section>

                    <apex:form >
                        <apex:repeat value="{!distanceMatrix}" var="dm">
                            <div class="dist_mat_row row">
                                <div class="col-md-2">
                                    <img src="{! URLFOR($Resource[dm['resImg']]) }" />
                                    <p>{!dm['distance']}<br />{!dm['duration']}</p>
                                </div>
                                <div class="col-md-5">
                                    <p>{!locInfos[ VALUE(dm['index']) ]['parent']}<br />
                                    {!locInfos[ VALUE(dm['index']) ]['street']}<br />
                                    {!locInfos[ VALUE(dm['index']) ]['city']}, {!locInfos[ VALUE(dm['index']) ]['state']} {!locInfos[ VALUE(dm['index']) ]['postal']}<br />
                                    {!locInfos[ VALUE(dm['index']) ]['phone']}</p>
                                    <p>{!locInfos[ VALUE(dm['index']) ]['status']}</p>
                                </div>
                                <div class="col-md-5">
                                    <label><apex:inputCheckbox selected="{!locInfos[ VALUE(dm['index']) ]['s1']}" disabled="true" /> <span>Service 1</span></label><br />
                                    <label><apex:inputCheckbox selected="{!locInfos[ VALUE(dm['index']) ]['s2']}" disabled="true" /> <span>Service 2</span></label><br />
                                </div>
                            </div>

                            <div class="dist_mat_row2 row">
                                <div class="col-md-2">
                                    <p>Referrals</p>
                                </div>
                                <div class="col-md-6">
                                    <p>Trained</p>
                                </div>
                                <div class="col-md-4">
                                    <a href="javascript:ref2Provider('{!locInfos[ VALUE(dm['index']) ]['provLocId']}');" class="right btn btn-lrg btn-success">Refer</a>
                                </div>
                            </div>
                        </apex:repeat>
                    </apex:form>

                </apex:pageBlock>
                <div class="col-md-12 dmpager hidden">                
                    <ul class="pager">
                        <li><a href="javascript:showPagedDistMatMapMarker(-5);">Previous 5</a></li>
                        <li><a href="javascript:javascript:showPagedDistMatMapMarker(5);">Next 5</a></li>
                    </ul>
                </div>
            </div>

            <div class="well col-md-6 map-container">
                <apex:form >
                    <apex:pageBlock >
                        <apex:commandButton action="{!distMatrixCallout}" id="getDistMatBtn"
                            value="Get Map" reRender="responseBlock" status="pageStatus" styleClass="hidden"/>
                    </apex:pageBlock>
                </apex:form>

                <apex:pageBlock id="mapBlock">
                    <apex:actionStatus id="mapStatus">
                        <apex:facet name="start">
                            <div class="loader">Loading...</div>
                        </apex:facet>
                        <apex:facet name="stop">
                            <script>
                                var theMapEl = j$('.the-map');
                                theMapEl.removeClass('hidden');
                                {!IF(resultsAvailable,"j$('[id*=getDistMatBtn]').click();", "")}
                            </script>
                        </apex:facet>
                    </apex:actionStatus>

                    <div class="the-map hidden">
                        <apex:map width="100%" height="950px">
                            <apex:mapMarker title="Home"
                                position="{!currentLocation}"
                                icon="{! URLFOR($Resource.MapHouse) }"/>

                            <apex:repeat value="{!locations}" var="loc">
                                <apex:mapMarker title="{!locInfos[loc['index']]['name']}"
                                    position="{!loc}"
                                    icon="{! URLFOR($Resource[locInfos[loc['index']]['resImg']]) }">

                                    <apex:mapInfoWindow >
                                        <apex:outputPanel layout="block" style="font-weight: bold;">
                                            <apex:outputText >{!locInfos[loc['index']]['name']}</apex:outputText>
                                        </apex:outputPanel>
                                        <apex:outputPanel layout="block">
                                            <apex:outputText >{!locInfos[loc['index']]['street']}</apex:outputText>
                                        </apex:outputPanel>
                                        <apex:outputPanel layout="block">
                                            <apex:outputText >{!locInfos[loc['index']]['city']}, {!locInfos[loc['index']]['state']} {!locInfos[loc['index']]['postal']}</apex:outputText>
                                        </apex:outputPanel>
                                        <apex:outputPanel layout="block">
                                            <apex:outputText >{!locInfos[loc['index']]['phone']}</apex:outputText>
                                        </apex:outputPanel>
                                    </apex:mapInfoWindow>
                                </apex:mapMarker>
                            </apex:repeat>
                        </apex:map>
                    </div>
                </apex:pageBlock>
            </div>
        </div>        
    </body>
</html>
</apex:page>

Controller:

public class ProviderLocMapDistanceMatrix {

    public List<Map<String,Double>> locations;
    public List<Map<String,String>> locInfos;
    public Map<String,Double> currentLocation;
    public String currentAddress;
    public decimal latitude;
    public decimal longitude;
    public decimal radius;
    public integer numLimit;
    private List <Account> accounts;
    private String continueDistMatrixId;
    public List<Map<String,String>> distanceMatrix {get;set;}
    public Map<String,String> response {get;set;}
    public Boolean resultsAvailable;
    public Boolean matrixAvailable;
    public List<String> contractTypes;
    public List<String> serviceTypes;

    public Boolean service1 {get;set;}
    public Boolean service2 {get;set;}
    public Boolean service3 {get;set;}
    public Boolean service4 {get;set;}
    public Boolean contract1 {get;set;}
    public Boolean contract2 {get;set;}
    public Boolean contract3 {get;set;}
    public Boolean contract4 {get;set;}

    public List<Map<String,Double>> getLocations(){
        return locations;
    }

    public List<Map<String,String>> getLocInfos(){
        return locInfos;
    }

    public decimal getLatitude(){
        if(latitude == Null){
            return 38.897676;
        }

        return latitude;
    }

    public void setLatitude(decimal latitude){
        this.latitude = latitude;
    }

    public decimal getLongitude(){
        if(longitude == Null){
            return -77.036483;
        }

        return longitude;
    }

    public void setLongitude(decimal longitude){
        this.longitude = longitude;
    }

    public decimal getRadius(){
        if(radius == Null){
            return 50;
        }
        return radius;
    }

    public void setRadius(decimal radius){
        this.radius = radius;
    }

    public decimal getNumLimit(){
        if(numLimit == Null){
            return 20;
        }
        return numLimit;
    }

    public void setNumLimit(integer numLimit){
        this.numLimit = numLimit;
    }

    public Boolean getResultsAvailable() {
        if(locations == Null) {
            return false;
        }
        return true;
    }

    public Boolean getMatrixAvailable() {        
        if(distanceMatrix == Null) {
            return false;
        }
        return true;
    }

    public Map<String,Double> getCurrentLocation(){
        if(currentLocation == Null){
            currentLocation = new Map<String,Double>();
            currentLocation.put('latitude', getLatitude());
            currentLocation.put('longitude', getLongitude());
        }

        return currentLocation;
    }

    public String getCurrentAddress(){
        if(currentAddress == Null){
            currentAddress = '1600 Pennsylvania Ave NW, Washington, DC 20500';
        }

        return currentAddress;
    }

    public String buildQuery(){

        accounts = new List<Account>();

        parseContracts();
        parseServices();

        String queryString =
            'SELECT ID, Name, ShippingStreet, ShippingCity, ShippingState, ShippingCountry, '+
            'ShippingPostalCode, ShippingLatitude, ShippingLongitude, Parent.Name, Phone, Account_Status__c, Clinic_Services__c from Account ' +
            'WHERE RecordType.Name=\'Clinic Account\' ';

        if(null != contractTypes){
            queryString += 'AND Account_Status__c IN(';

            for(String ct : contractTypes){
                queryString += '\''+ct+'\',';
            }

            queryString = queryString.removeEnd(',');  //remove last comma

            queryString += ') ';
        }

        if(serviceTypes.size() > 0){
            queryString += 'AND Clinic_Services__c includes(';

            queryString += '\'';

            for(String st : serviceTypes){
                queryString += st+';';
            }

            queryString = queryString.removeEnd(';');  //remove last comma

            queryString += '\'';

            queryString += ') ';
        }

        queryString +=
            'AND DISTANCE(ShippingAddress, GEOLOCATION('+getLatitude()+','+getLongitude()+'), \'mi\') < ' + getRadius()+' ' +
            'ORDER BY DISTANCE(ShippingAddress, GEOLOCATION('+getLatitude()+','+getLongitude()+'), \'mi\') ' +
            'LIMIT '+getNumLimit();

        return queryString;
    }

    public void parseServices(){
        serviceTypes = new List<String>();

        if(service1 == true){
            serviceTypes.add('Service 1');
        }

        if(service2 == true){
            serviceTypes.add('Service 2');
        }

        if(service3 == true){
            serviceTypes.add('Service 3');
        }

        if(service4 == true){
            serviceTypes.add('Service 4');
        }
    }

    public void parseContracts(){
        contractTypes = new List<String>();

        if(contract1 == true){
            contractTypes.add('Contract 1');
        }

        if(contract2 == true){
            contractTypes.add('Contract2');
        }

        if(contract3 == true){
            contractTypes.add('Contract 3');
        }

        if(contract4 == true){
            contractTypes.add('Contract 4');
        }

        // no contract types provided, use all types
        if(contractTypes.size() == 0){
            contractTypes.add('Contract 1');
            contractTypes.add('Contract 2');
            contractTypes.add('Contract 3');
            contractTypes.add('Contract 4');            
        }
    }

    public PageReference findLocations(){

        accounts = database.Query(buildQuery());

        if(accounts.size() > 0) {                   
            // Convert to locations that can be mapped
            locations = new List<Map<String,Double>>();            
            locInfos = new List<Map<String,String>>();

            Integer x = 0;

            for (Account a : accounts) {                
                locations.add(
                    new Map<String,Double>{
                        'latitude' => a.ShippingLatitude,
                        'longitude' => a.ShippingLongitude,
                        'index' => x
                    }
                );

                String resImg;  // based on account status, set resImg for map marker

                if(a.Account_Status__c == 'Contract 1'){
                    resImg = 'MapBlueCircle';
                }

                if(a.Account_Status__c == 'Contract 2'){
                    resImg = 'MapTurqCircle';
                }

                if(a.Account_Status__c == 'Contract 3'){
                    resImg = 'MapOrangeCircle';
                }

                if(a.Account_Status__c == 'Contract 4'){
                    resImg = 'MapPinkCircle';
                }                

                List<String> services = a.Clinic_Services__c.split(';');
                Boolean s1 = false;
                Boolean s2 = false;
                for(String service : services){
                    if(service.equals('Service 1')){
                        s1 = true;
                    }

                    if(service.equals('Service 2')){
                        s2 = true;
                    }
                }

                locInfos.add(
                    new Map<String,String>{
                        'name' => a.Name,
                        'street' => a.ShippingStreet,
                        'city' => a.ShippingCity,
                        'state' => a.ShippingState,
                        'postal' => a.ShippingPostalCode,
                        'country' => a.ShippingCountry,
                        'phone' => null != a.Phone ? a.Phone : '<No Phone>',
                        'resImg' => resImg,
                        'parent' => a.Parent.Name,
                        'status' => a.Account_Status__c,
                        'provLocId' => a.Id,
                        's1' => (s1) ? 'true' : 'false',
                        's2' => (s2) ? 'true' : 'false'
                    }
                );

                x++;
            }
        }

        return null;
    }


    public Object distMatrixCallout(){
        String origin = EncodingUtil.urlEncode(
            '1600 Pennsylvania Ave NW, Washington, DC 20500',
            'UTF-8'
        );

        accounts = database.Query(buildQuery());

        String destString = destinationsToString();

        Http http = new Http();
        HttpRequest request = new HttpRequest();

        String endpoint = 'https://maps.googleapis.com/maps/api/distancematrix/json'
            + '?key=KEYHERE'
            + '&origins=' + origin
            + '&destinations=' + destString
            + '&mode=driving'
            + '&sensor=false'
            + '&language=en'
            + '&units=imperial';

        request.setEndpoint(endpoint);
        request.setMethod('GET');

        HttpResponse response = http.send(request);
        // If the request is successful, parse the JSON response.
        if (response.getStatusCode() == 200) {
            distanceMatrix = new List<Map<String,String>>();

            // Deserialize the JSON to a map of key => value
            Map<String, Object> root = (Map <String, Object>) JSON.deserializeUntyped(response.getBody());

            // Get the List of objects from destination_addresses
            List<Object> jsonDestAddresses = (List<Object>) root.get('destination_addresses');

            List<String> destinations = new List<String>();

            // Go through each of the jsonDestAddresses
            for(Object obj : jsonDestAddresses){
                String destStr = (String)obj;
                destStr = destStr.replace('{address=', '').replace('}', '');

                destinations.add(destStr);
            }

            List<Object> jsonRows = (List<Object>) root.get('rows');
            for(Object rowObj : jsonRows){

                // Cast the object to a map of key => value
                Map<String, Object> rowMap = (Map<String, Object>) rowObj;

                Integer index = 0;

                List<Object> jsonElements = (List<Object>) rowMap.get('elements');
                for(Object elem : jsonElements){
                    String distStr = '';
                    String durStr = '';                

                    String elemStr = String.valueOf(elem);
                    elemStr = elemStr.replaceAll(', ', ',');
                    elemStr = elemStr.substring(1, elemStr.length());  //get rid of first curly brace
                    elemStr = elemStr.substring(0,elemStr.length()-1);  //get rid of last curly brace

                    List<String> elemParts = elemStr.split('},');
                    for(String part : elemParts){

                        if(part.contains('distance')){
                            part = part.replace('distance={', '');
                            part = part.replace('text=', '');
                            List<String> valueParts = part.split(',value');
                            distStr = valueParts[0];
                        }

                        if(part.contains('duration')){                        
                            part = part.replace('duration={', '');
                            part = part.replace('text=', '');
                            List<String> valueParts = part.split(',value');
                            durStr = valueParts[0];
                        }
                    }

                    String resImg;  // based on account status, set resImg for map marker


                    if(accounts.get(index).Account_Status__c == 'Contract 1'){
                        resImg = 'MapBlueCircle';
                    }

                    if(accounts.get(index).Account_Status__c == 'Contract 2'){
                        resImg = 'MapTurqCircle';
                    }

                    if(accounts.get(index).Account_Status__c == 'Contract 3'){
                        resImg = 'MapOrangeCircle';
                    }

                    if(accounts.get(index).Account_Status__c == 'Contract 4'){
                        resImg = 'MapPinkCircle';
                    }

                    distanceMatrix.add(
                        new Map<String,String>{
                            'distance' => distStr,
                            'duration' => durStr,
                            'index' => String.valueOf(index),
                            'resImg' => resImg
                        }
                    );

                    index++;
                }
            }

        }
        return null;
    }

    private String destinationsToString(){
        String destString = '';

        for(Account dest : accounts){

            String d = EncodingUtil.urlEncode(
                + dest.ShippingStreet + ' '
                + dest.ShippingCity + ', '
                + dest.ShippingState + ' '
                + dest.ShippingPostalCode + ' '
                + dest.ShippingCountry,
                'UTF-8');

            destString += d + '%7C';  //urlencoded pipe (|)
        }

        return destString.removeEnd('%7C');
    }
}

On the first submission with no options checked I get the following logs:

09:10:42:013 USER_DEBUG [189]|DEBUG|queryString: SELECT ID, Name, ShippingStreet, ShippingCity, ShippingState, ShippingCountry, ShippingPostalCode, ShippingLatitude, ShippingLongitude, Parent.Name, Phone, Account_Status__c, Clinic_Services__c from Account WHERE RecordType.Name='Clinic Account' AND Account_Status__c IN('Contract 1','Contract 2','Contract 4','Contract 3') AND DISTANCE(ShippingAddress, GEOLOCATION(39.011817,-94.663452), 'mi') < 50 ORDER BY DISTANCE(ShippingAddress, GEOLOCATION(39.011817,-94.663452), 'mi') LIMIT 20

09:10:42:028 USER_DEBUG [247]|DEBUG|accounts.size(): 16

Then I resubmit with checking the first option and I get these logs:

09:11:12:017 USER_DEBUG [189]|DEBUG|queryString: SELECT ID, Name, ShippingStreet, ShippingCity, ShippingState, ShippingCountry, ShippingPostalCode, ShippingLatitude, ShippingLongitude, Parent.Name, Phone, Account_Status__c, Clinic_Services__c from Account WHERE RecordType.Name='Clinic Account' AND Account_Status__c IN('Contract 1','Contract 2','Contract 4','Contract 3') AND Clinic_Services__c includes('Service 1') AND DISTANCE(ShippingAddress, GEOLOCATION(39.011817,-94.663452), 'mi') < 50 ORDER BY DISTANCE(ShippingAddress, GEOLOCATION(39.011817,-94.663452), 'mi') LIMIT 20

09:11:12:033 USER_DEBUG [247]|DEBUG|accounts.size(): 10

Subscript value 10 not valid. Must be between 0 and 9

  • Have you checked your logs? Any indication as to a line number? – sfdcfox Apr 26 '17 at 13:40
  • @sfdcfox there aren't any line numbers listed and no errors in my log. – Scott Apr 26 '17 at 13:48
1

One of your apex:repeat's are going out of range on a List. Specifically, it is trying to access index 10 on a List that only contains 10 elements (i.e. the correct indexes are 0 to 9).

As a guess from the log alone, it might be trying to access the 11th Account when the size was reported as 10.

It could equally be locInfos or any of the other List<X> collections that Visualforce is binding to.

Check the locations map index double. Those values could be out of range for the subsequent access to the locInfos. I'd probably augment your Apex code with some sanity checks to make sure the values are lining up as expected.

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.