5

I have been trying for a good few hours to only get my apex:outputLink to show up if it is a key within a map in my controller.

It keeps giving me the generic 'Syntax Error'.

Here's the VF:

<apex:repeat value="{!alphabet}" var="a">
        <apex:outputLink value="{!a}" rendered="{!IF({!cons}.get({!a}) == null, true, false)}">{!a}</apex:outputLink>                       
        &nbsp;|&nbsp;
</apex:repeat>

So "cons" is a Map>() that gets loaded in the constructor. "alphabet" is a simple List of Strings - "A", "B", "C" all the way to "Z".

Basically I would like to know if cons.get("A") == null, cons.get("B") == null, and only show the outputLink if this is not the case.

EDIT: I've tried with:

rendered="{!IF(!cons[a] != null, true, false)}"

which at least compiles, but gives me the error "Map key A not found in map"

EDIT2: I've made sure cons contains all the keys. So now I'm looking to see if the associated List size is greater than 0. However

<apex:outputLink value="{!a}" rendered="{!IF(cons[a].size!=0, true, false)}">{!a}</apex:outputLink>

gives the following: Incorrect parameter for subscript. Expected Number, received Text

EDIT3 - UPDATE FOR PETER

Controller:

public Map<String, ContactListViewController.ListWrapper> cons {get;set;}

private class ListWrapper{

    public List<Contact> theList {get;set;}

    public ListWrapper(){}

    public ListWrapper(List<Contact> theList){

        this.theList = theList; 
    }

    public Integer size {

        get {
            return theList.size();
        }
    }
}

VF:

<apex:repeat value="{!alphabet}" var="a">
                <apex:outputLink value="{!a}" rendered="{!IF(cons[a].size>0, true, false)}">{!a}</apex:outputLink>                      
                &nbsp;|&nbsp;
</apex:repeat>

This code is working fine.

1
  • Great! Half the battle with coding Apex is figuring out the workaround for stuff that seems like it should work but just doesn't. Sep 20, 2012 at 18:04

4 Answers 4

5

It is not possible to check whether a Map in your controller has a key in it from your VF page in a dynamic fashion the way that you were trying. From your VF page, if you try to access a Map with a key that does not map to a value you will get an error. See the VF developers guide section on Lists and Maps for more information.

I see that you fixed that in EDIT 2, by making sure that all keys are in cons, so that solves that part.

Now you are getting the error: Incorrect parameter for subscript. Expected Number, received Text

<apex:outputLink value="{!a}" rendered="{!IF(cons[a].size!=0, true, false)}">{!a}</apex:outputLink>

I reproduced this error. I can access cons[a][0] and get the 0th element of each List in each Map entry and I can also just write out cons[a] without an issue. It is just an issue when trying to access size. I'm not totally sure why.

To get around your problem you could add an extra layer of indirection to wrap the list and get the size differently: Update: put the code inside of a class

public class ListWrapperController {
    public class ListWrapper {
        public ListWrapper(List<String> t) {
            theList = t;
        }
        public List<String> theList { get; set; }
        public Integer size { 
            get { 
                return theList.size(); 
            }
        }
    }

    public List<String> alphabet { 
        get { 
            return new List<String>{'a','b','c','d','e'}; 
        }
    }
    public Map<String, ListWrapper> cons { 
        get { 
            return new Map<String, ListWrapper>{
                'a'=>new ListWrapper(new List<String>()), 
                'b'=>new ListWrapper(new List<String>{'bbbb'}), 
                'c'=>new ListWrapper(new List<String>{'cccc'}), 
                'd'=>new ListWrapper(new List<String>{'dddd'}), 
                'e'=>new ListWrapper(new List<String>{'eeee'})
            }; 
        } 
    }
}

Then in the VF your call to size should work because it will be calling your wrapper class's size method (or whatever you name the method...doesn't have to be size) and not the actual Apex List's size method.

<apex:repeat value="{!alphabet}" var="a">
    <apex:outputLink value="{!a}" rendered="{!cons[a].size > 0}" >{!a}   </apex:outputLink>
</apex:repeat>

I tested that controller and that VF snippet and it ouptputted the following links (note no 'a' link since the ListWrapper that 'a' maps to has an empty list):

b c d e

6
  • Key word is "should" :) The VF complained that there was no 'Id' in the ListWrapper, and when I added a token attribute id complained there was no 'Name'. Makes me think that only Lists of Sobjects work and a wrapper is not possible.
    – George S.
    Sep 19, 2012 at 12:45
  • @user449 - I updated my code sample and I was able to get the size to output in my VF. I think that the error that you are getting could be due to the VF needing updating. Can you double check your VF to make sure that after changing to use the wrapper that all of your references access the wrapper where appropriate (e.g., wrapper[a].theList and not wrapper[a].Id, or however you have it wrapped. Sep 19, 2012 at 13:00
  • Please see my third edit above. I really appreciate your time.
    – George S.
    Sep 19, 2012 at 13:24
  • 1
    The error above was in a different section of the VF. The code is now working. Thanks so much.
    – George S.
    Sep 19, 2012 at 13:42
  • I just ran into something similar enough to add a note. If you have a map with valid keys pointing to null values (e.g. thisMap['validKey'] = null, this will ALSO trigger an error in Visualforce. You can't use formulas to test whether the value is null. (This is documented, but easy to miss.)
    – Benj
    Sep 11, 2013 at 21:40
2

You're pretty close. In your first example at least, you had a few syntax type mistakes.

Example: rendered="{!IF({!cons}.get({!a}) == null, true, false)}" You have extra curly braces and exclamation marks around cons and a. Also, you cannot access the get method like that. In visualforce, you simply surround the key with square brackets like "cons[a]"

Also, rao is close as well, but he's only looking at the 0th element in the list instead of all the keys.

I've implemented a similar rolodex before, think it was using very similar if not nearly identical logic.

<apex:repeat var="letter" value="{!alphabetList}">
  <apex:outputLink value="{!letter}" rendered="{!NOT(ISBLANK(cons[letter]))}">{!letter}</apex:outputLink>
  <apex:outputText rendered="{!ISBLANK(cons[letter])}">{!letter}</apex:outputText>
  &nbsp;|&nbsp;
</apex:repeat>

The above Visualforce should output a link if a letter has records associated with it, else it will output the character without a link. You can also substitute commandLink for outputLink and link back to an action in your controller. Another option as well is to use the same link for all letters in the alphabet, but then use the apex:param child element under apex:outputLink to pass the letter of the alphabet with the link if that makes sense.

4
  • 1
    I think your IF here needs the ,true,false also wouldn't this still fail if the key wasn't in the map?
    – Phil B
    May 8, 2014 at 13:37
  • 1
    actually wouldn't just removing the IF resolve into a true/false from ISBLANK?
    – Phil B
    May 8, 2014 at 14:32
  • Indeed. Fixed.. May 8, 2014 at 15:18
  • {!ISBLANK(cons[letter])} is the only thing that works in this case apart form creating a wrapper for the list
    – zokito
    Sep 11, 2014 at 20:45
0

you can do the one below : public Map> accountsMap {get; set;}

Refer : http://bobbuzzard.blogspot.com/2011/03/visualforce-dynamic-map-bindings.html

LMK if this helped :)

1
  • See my edit above. I'm doing something similar to the link, but can't quite get it right.
    – George S.
    Sep 18, 2012 at 20:47
0

Cleaner version :

<apex:page controller="testconr" > 
<apex:form > 
<apex:repeat value="{!acc}" var="a"> 
<apex:outputLink value="{!a}" rendered="{!If(ISBLANK(cons[0]),true,false)}"> hai </apex:outputLink>
</apex:repeat> 
</apex:form>
</apex:page>

controller :

public with sharing class testconr {
public map<integer,string>cons{get;set;}
List<Account> acc = [select id,name from account limit 10];

public list<account> getAcc() {
cons = new map<integer,string>();
system.debug('ACC VALUE IS' + acc);
for( integer i=0;i<acc.size();i++){
cons.put(i,acc[i].name);
}
    return acc;
}

}
1
  • Use rendered="{!If(NOT(ISBLANK(cons[0])),true,false)}"> to check for not null
    – Rao
    Sep 18, 2012 at 21:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .