Sunday, December 6, 2015

Keep it simple stupid: an example from Comcast

I know its been a while since my last blog post, and when I look back at some of my older posts, I see so much I would like to update or correct. But isn't that always the way when we look back on our work? If its from a few years ago or a few months ago, since we are always learning from each task we take on, looking back on any of our work should bring with it insight on how it could be better. I think thats one of the most important and amazing parts of being in this engineering role, being able to look at something (a process, a website, a user interface, a system, a toy, whatever) and see how to improve it. Or at the very least recognize that it could be improved and that there is something not quite right about it.

Lets look at an example of something, that to me, isn't quite right. I happen to be a customer of Comcast or as its also know Xfinity. Aside from any personal experiences with the company you may have, they are a large company and have a large user base. They need to make sure their user interfaces, (the remote, the guide, on demand etc) are clear and simple for all audiences to be able to know exactly what to do on a page right when they land on it. It needs to be out right obvious what the purpose of the page is and to guide the user, making it easy to use and thus creating a good user experience. I've watched them through updates, each time removing some clunkiness from the overall user process, but generally just changing the look and feel. This being said, it is still one of the better TV interfaces I've seen, taking queues from Hulu and Netflix and merging it with their own blend of stuff. It feels like they are making progress but missing some of the small wins that would make it better as a whole, and round off the user experience. 

I want to share one such example that to me says a lot about whats wrong with so many user interfaces out there. In this one example I can see more than a few things 'wrong' but lest see if you agree with me. 

Here is the prompt in Comcast that pops up when you finish watching a recording:


When looking at something, as a user, I should be able to glance and with less than a fraction of a second be able to know where to go next. But, when I first look at this, I see the word 'Delete' in BOTH of the buttons! So instantly I am forced to take an extra second to then read the text and options again. My next thought is why are there more than one word in each button? Couldn't we keep it simple and use just 'Delete' and 'Save'? Instead we are a) confusing the user with delete in each button and b) making the verbiage more complicated than it needs to be. The next issue I see is that the potentially negative action is selected by default. By doing this, if the user miss reads the buttons (and as I have outlined that is a very possible outcome) they may just click on the first selected option assuming that its the action they want because it is highlighted by default. I would think that as an added precaution to the user to have to switch over to the 'Delete' button would be one more affirmation that, YES I do want to delete this. We could go one step further and color the text if we wanted with a red for Delete and some sort of neutral color for Save, but in general just switching these buttons to be single words and not using delete in each text would help immensely.

Such a small change for such a big impact. 

I use this example because I mistakenly deleted a recording while my toddler was distracting me, and it was the recording that he wanted to watch again! After I tempered his tantrum, my first thought was to look for some sort of 'deleted recordings' queue, maybe they would give me a window of time to change my mind and undelete, but alas they do not.. at least not that I could find. But it got me thinking about why it happened, and how it could have been prevented. It also reminds me to keep it simple in my own work, and try to remove this sort of clunkiness from any process I may encounter. I only use this example because of how easily it can be identified with, we have all been there, and how easily it can be translated to all of our own it work. Lets take a lesson from this and continue to strive to bring better interfaces to our end users! 

Wednesday, April 9, 2014

Troubleshooting of Set's with sObjects in an apex:repeat


Below is the troubleshooting steps/thought process I took to figure out why my Set wasn't working when it contained sObjects in an apex:repeat. Long story short, its just not possible, it seems that since sets are unordered lists, it cant iterate over sObjects.. I think it still should be able to :-P


I have some code that creates a set (using set so it will be unique) of sObjects and then on the page it iterates over the set using an apex:repeat. The problem is that it wont save. If I change the Set to a List it saves and works fine. Here is the code (simplified) and the errors:

    public class CreateOrders {

      public Set<Item__c> selectedItems {get; set;}
      
      public CreateOrders() {
        selectedItems = new Set<Item__c>();
        for (Item__c i : findItems()) {
          selectedItems.add(i);
        }
      }
    }

then on the page:
    
    <apex:repeat value="{!selectedItems}" var="itm">
      <apex:outputfield value="{!itm.Name}" />
    </apex:repeat>

and the resulting error is: 

Save error: Could not resolve the entity from apex:outputField value binding '{!itm.Name}'.  apex:outputField can only be used with SObjects, or objects that are Visualforce field component resolvable. 

When I try removing the apex:outputfield by using apex:outputtext or nothing like so:

    <apex:repeat value="{!selectedItems}" var="itm">
      <apex:outputtext value="{!itm.Name}" />
    </apex:repeat>

OR

    <apex:repeat value="{!selectedItems}" var="itm">
      {!itm.Name}
    </apex:repeat>

I get this error: 

Save error: Unknown property 'SetValue.Name' 

Its the same with any field/object. But when I switch the Set over to a List it works without any issues... So on a hunch I checked the version, and it was on 28. When I upgraded it to 29, it allowed me to save with apex:outputfield only, but on refresh of the page I still got the same errors:

Could not resolve the entity from apex:outputField value binding '{!itm.Name}'. apex:outputField can only be used with SObjects, or objects that are Visualforce field component resolvable.

It still wouldn't let me save if I changed the apex:outputfield to apex:outputtext or used no apex tags it would still not save and give me the error:

Save error: Unknown property 'SetValue.Name'

On versions older than 29 (20-28 that I have tested) they all error like 28, 29 is the only one with a slightly different behavior. Also when saving with just {!itm} it allows the save to happen, and the page just shows the Id of the Item.


Here is the original stackexchange post with comments from other sfdc users as we tried to figure out this problem, along with the answer :-P I'll be posting more on sets/maps soon :-P

Tuesday, June 18, 2013

Custom Component Controllers and using assignTo

So I have been working with components lately and have been pretty aggravated that I couldn't seem to get the assignTo to work correctly. So I followed the example showen in the documentation (Custom Component Controllers) word for word, and it worked.. but not really.  Once I tried switching the get/set to the way I prefer public String controllerValue {get; set;} it stopped working. I also noticed that if I was to put anything in the constructor it access it, it would create a null error. For example, if you were to use the same example as in the documentation and changed the class to:

public with sharing class sgf_ComponentAssignToIssue {

  public String controllerValue;
    
  public void setControllerValue (String s) {
    controllerValue = s.toUpperCase();
  }
    
  public String getControllerValue() {
    return controllerValue;
  }

  public sgf_ComponentAssignToIssue() {
    if (String.isNotBlank(controllerValue)) {
      controllerValue.toLowerCase();
      return;
    }
    ApexPages.addMessage(new ApexPages.Message(
      ApexPages.Severity.ERROR, 
      'string is blank'
    ));
  }

}

and change the page to:

<apex:component controller="sgf_ComponentAssignToIssue">
  <apex:attribute name="componentValue" description="Attribute on the component." type="String" required="required" assignTo="{!controllerValue}" />
    <apex:pageBlock title="My Custom Component">
      <p>
        <code>componentValue</code> is "{!componentValue}"
        <br/>
        <code>controllerValue</code> is "{!controllerValue}"
      </p>
    </apex:pageBlock>
    Notice that the controllerValue has been upper cased using an Apex method.

  <!-- just adding the following -->
  <br />
  <apex:pageMessages id="messages" />

</apex:component>

the page will error:


If I change the class to:

public with sharing class sgf_ComponentAssignToIssue {

  public String controllerValue {
    get {return controllerValue.toUpperCase();} 
    set;
  }
    
  public sgf_ComponentAssignToIssue() {
    if (String.isNotBlank(controllerValue)) {
      controllerValue.toLowerCase();
      return;
    }
    ApexPages.addMessage(new ApexPages.Message(
      ApexPages.Severity.ERROR, 
      'string is blank'
    ));
  }
}

and leave the page as is, the page will now error:


If I change it so the class looks like this:

public with sharing class sgf_ComponentAssignToIssue {

  public String controllerValue {
    get;
    set {controllerValue = value.toUpperCase();}
  }
    
  public sgf_ComponentAssignToIssue() {
    if (String.isNotBlank(controllerValue)) {
      controllerValue.toLowerCase();
      return;
    }
    ApexPages.addMessage(new ApexPages.Message(
      ApexPages.Severity.ERROR, 
      'string is blank'
    ));
  }

}

The page will work again, but still nothing works in the constructor. And if I change it to say, an Id and update the class to:

public with sharing class sgf_ComponentAssignToIssue {

  public Id controllerValue {get; set;}
  public Account passedAccount {get; set;}
    
  public sgf_ComponentAssignToIssue() {
    if (String.isNotBlank((String)controllerValue)) {
      passedAccount = new Account(Id = controllerValue);
      return;
    }
    ApexPages.addMessage(new ApexPages.Message(
      ApexPages.Severity.ERROR, 
      'Id is blank'
    ));
  }

}

and the page to pass the Id to the controller, you get:
So you can see that the controller does effect the variable but still since I can not use it in the constructor I cant really use it in the component to do anything on load... 

My Question is:
Why cant the class constructor or the getter access the variable being passed from the assignTo?

Tuesday, June 4, 2013

Showing images in your custom Visualforce pages via Attachments

Everyone wants images, profile pictures, logos etc. Sure you can add these via style, but this isn't very practical if the end user is adding them (they dont know, or need to know, how to use static resources). So lets say you have a task to make a page that shows lists of contacts, but they want to see the contacts image, if they have one. If they dont, we will have to show a default user image in its place to keep each contact consistant. 

This is done very simply :-) On each contact record (or any object that you allow attachments on) the user has the ability to attach files and images. So we want to grab them via a relationship query on that object, and then display them on the page. For my example I am only going to show the most recent image per Contact.

First step is to create the page and controller. I called my class (controller) sfg_ContactPhotos so the page so far would look like this:


<apex:page showHeader="true" sidebar="true" controller="sfg_ContactPhotos">

</apex:page>

In the controller we will need a List of Contacts and a few methods to get it all going. Normally this would be a controller extension to the Contact standard controller, but since I just want to show the logic, I dont want to complicate things here. 



public class sfg_ContactPhotos {

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


  public sfg_ContactPhotos() {

    //constructor
  }

  private List<Contact> findContacts() {

    //logic for getting contacts
    return new List<Contact>();
  }

}



You may notice that I like to use the {get; set;} instead of the way salesforce normally does it via a get method and set method; this is simply because if it is done the salesforce way, the get method will be hit on every call to the controller and I only need to load the Contacts once. Anyhow, we need to add another variable for the Map of Attachments, a method to build the map, and it wouldn't hurt to wire up the variables to their methods in the constructor. If you notice the Map is a <String, String> which is not the *correct way, it should be Map<Id, Attachment> but due to issue with visualforce and Maps, it has to be String (see Post).


public class sfg_ContactPhotos {

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

  public Map<String, String> mapOfPhotos {get; set;}

  public sfg_ContactPhotos() {

    lstOfContacts = findContacts();
    mapOfPhotos = buildPhotoMap(lstOfContacts);
  }

  private List<Contact> findContacts() {

    //logic for getting contacts
    return new List<Contact>();
  }

  private Map<String, String> buildPhotoMap(List<Contact> pContacts) {

    if (pContacts  == null || pContacts.isEmpty()) {
      return new Map<String, String>();
    }

    Map<String, String> returnMap = new Map<String, String>();

    //logic to make the map of attachments
    return returnMap;
  }

}



A few things to note here, if you notice I am passing the list of Contacts into the buildPhotoMap method. I call it 'pContacts' due to it being a 'param' thus the 'p'. Since it is passed, this means that we can control what builds the map, instead of just grabbing the Contacts in their current state. I also check to see if what is passed actually has anything before creating any variables in the class, this is a good practice to get into. Why waste time creating variables if there isn't anything for the method to use? It may only be milliseconds but it can add up, so its good to do the check for null/empty/blank first and if nothing is there, exit the method, not wasting any time. 


Ok, lets add some logic to the class, first we will query for the Contacts and the related Attachments, then pass it to the buildPhotoMap method to get the data set up like we want. In this query we only want the public ones and will order the Attachments by LastModifiedDate 'desc' which pust the newest one first. We will limit the query to 2 just so we dont over load the page/example.


private List<Contact> findContacts() {
    return [select Name,
                   Email,
                   (select ContentType
                    from Attachments
                    where isPrivate = false
                    order by LastModifiedDate desc)
            from Contact
            limit 2];
  }


If you notice I am not querying for any Id's this is because by default SOQL returns the Id of the object so there is no need to specify it in the query. There is also no need for a try catch statement since if there is nothing to return it will simply return null, and in the buildPhotoMap method we are if it is null or empty before using it, thus negating any issues.

Now lets put some logic into the buildPhotoMap method, we will need to iterate over the contact list with a for loop and since the Attachments are in a related query we will need a second for loop over it to get into each attachment on the individual contact record. Once in the Attachments for loop we will need to check what type it is since you can have many different types of attachments, we should check if the ContentType is of type 'image'. (you could of corse add a where clause to the query itself and only return Attachments where ContentType = 'image/%' thus negating the check in the for loop)



private Map<String, String> buildPhotoMap(List<Contact> pContacts) {
    if (pContacts.isEmpty()) {
      return new Map<String, String>();
    }

    Map<String, String> returnMap = new Map<String, String>();
    for (Contact c : pContacts) {
      for (Attachment a : c.Attachments) {
        if (returnMap.containsKey(c.Id)) {
          break;
        }
        if (a.ContentType.contains('image/')) {
          returnMap.put(c.Id, a.Id);
          break;
        }
      }
      if (!returnMap.containsKey(c.Id)) {
        returnMap.put(c.Id, 'none');
      }
    }
    return returnMap;
  }


Since we need a map entry for each contact (so we dont brake the page with an error of 'Map key {ID} not found in map') and we dont want to loop more than we need to, I set it up so it brakes once it detects that the map already contains the contact Id. If it doesn't then it will check if the Attachment is of type 'image/' (normally the types are 'image/jpg' or 'image/gif' etc so using 'image/' will cover all images) and if it is, add it to the map and then brake out of the Attachment for loop and on to the next contact. If however, the Contact doest have any image Attachments, we will add a null value so the map wont brake the page.

Now that we have the controller done, lets focus on the page. We will need an apex:repeat over the contact list and an apex:image to display the default image (incase the user has no images)


<apex:page showHeader="true" sidebar="true" controller="sfg_ContactPhotos">
    <apex:stylesheet value="{!URLFOR($Resource. sfg_customPhotos, '/css/main.css')}" />
 
    <div id="sfg_pageWrap">
        <apex:repeat value="{!lstOfContacts}" var="c">

            <div id="{!c.Id}">
                <h1>{!c.Name}</h1>
                <apex:image url="{!URLFOR($Resource.sfg_customPhotos, '/image/userIcon.png')}" width="50" height="50"/>
            </div>

        </apex:repeat>
    </div>
</apex:page>


As you can see I have some style included via the apex:stylesheet tag, and a default image. We still need to add some conditional rendering and the Attachment Image to the page but first lets take a moment to talk about Resources.

Static Resources are very hand if you know how to use them. First off they are located in setup > develop > Static Resources. Click the new button and you'll see a page like this:

 The name will be the one used in the value="{!URLFOR($Resource.yourResourceNameHere,  ... ", the resource should be public, and the file should be a zip of the folder (if you want to have everything in one spot, otherwise you can just upload an image or a file, but if you want folders they have to be compressed or 'zipped'). Since I am including everything in one resource I have to zip my folders, which look like this on my computer:

So the page should now look like this once we fill it all out:
Click save and then if you need to update it you can click edit and upload the new .zip.

Since I am using folders, I have to travers them like would in any other language to access the items like '/css/main.css' as you can see in the page. Its the same for the image as well, '/image/userIcon.png'. Even if you dont have folders but a zip of lose files, you will need to '/someFileName.css' the '/' is important, without it some browsers (like IE) wont render it correctly.

Ok back to business, lets add the Attachment image to the page and then put in some conditional rendering based off weither the map is empty for that contact, and if so lets show a default image of a user:

<apex:image rendered="{!mapOfPhotos[c.Id] == 'none'}" url="{!URLFOR($Resource.sfg_customPhotos, '/image/userIcon.png')}" width="50" height="50"/>

<apex:image rendered="{!mapOfPhotos[c.Id] != 'none'}" url="{!URLFOR($Action.Attachment.Download, mapOfPhotos[c.Id])}" width="50" height="50" />

SO when we load the page we should see something like this:
...and yes .gif's do work :-)

Monday, May 27, 2013

Bug with apex:selectCheckboxes

The Project:
Make a search with multi-select checkboxes for better filtering. 

The Solution:
Simple right? Just make a page and controller with some <apex:selectCheckboxes> and a <apex:commandButton> to make it all go. 

<apex:page showHeader="true" sidebar="true" controller="sfg_testBugWithActionButton">
<apex:form>
    <apex:outputpanel id="mainWrap">
        <apex:repeat value="{!filterMap}" var="key">
          <div class="filterItem">
            <h2>{!key}</h2>
            <apex:selectCheckboxes value="{!filterKeys[key]}" layout="pageDirection">
              <apex:selectOptions value="{!filterMap[key]}" />
            </apex:selectCheckboxes>
          </div>
        </apex:repeat>
        <apex:commandButton action="{!preformAction}" rerender="renderWrap" value="Submit Action" />
      <apex:outputpanel id="renderWrap">
        {!resultString}
      </apex:outputpanel>
    </apex:outputpanel>
  </apex:form>
</apex:page>



public class sfg_testBugWithActionButton {

  public List<String> fGender {get; set;}

  public List<String> fGrade {get; set;}

  public List<String> fRole {get; set;}

  public String resultString {get; set;}



  public Map<String, List<SelectOption>> filterMap {get; set;}
  public Map<String, String> filterKeys {get; set;}

  public sfg_testBugWithActionButton() {
    filterKeys = new Map<String, String> {
      'Gender' => 'fGender',
      'Grade' => 'fGrade',
      'Role' => 'fRole'
    };
    createfilterMap();
    resultString = 'on Load of page';
  }

  public PageReference preformAction() {
    resultString = 'button action preformed';
    return null;
  }

  private void createfilterMap() {
    filterMap = new Map<String, List<SelectOption>>();
    List<SelectOption> options = new List<SelectOption>();

    for (String s : filterKeys.keySet()) {
      if (s == 'Gender') {
        options = new List<SelectOption>();
        options.add(new SelectOption('Male', 'Male'));
        options.add(new SelectOption('Female', 'Female'));
        filterMap.put('Gender', options);
      }
      if (s == 'Grade') {
        options = new List<SelectOption>();
        options.add(new SelectOption('A', 'A'));
        options.add(new SelectOption('B', 'B'));
        options.add(new SelectOption('C', 'C'));
        filterMap.put('Grade', options);
      }
      if (s == 'Role') {
        options = new List<SelectOption>();
        options.add(new SelectOption('Support', 'Support'));
        options.add(new SelectOption('Sales', 'Sales'));
        options.add(new SelectOption('Marketing', 'Marketing'));
        filterMap.put('Role', options);
      } 
    }
  }
}

(no style so nothing pretty, just to get the idea)

But WAIT! Why isn't anything happening when I click the button? The text next to the button should be changing. hummm Lets put a debug at the top of the method being called by the button and check the logs to see if its hit...

public PageReference preformAction() {
  system.debug('Gender: ' + fGender);
  system.debug('Grade: ' + fGrade);
  system.debug('Role: ' + fRole);
  resultString = 'button action preformed';
  return null;
}

hummm nothing.. not even a mention of the button getting clicked in the logs, let alone my debug statements. Ok I know from experience I can force the button by using the <apex:actionRegion> attribute..

<apex:actionRegion>
  <apex:commandButton action="{!preformAction}" rerender="renderWrap" value="Submit Action" />
</apex:actionRegion>
      
Click and well now the debug statement is being hit, but its not picking up the values in the  <apex:selectCheckboxes> hummmm.. I know I shouldnt need the <apex:actionRegion> so lets remove that and see what happens when i just click the button, without selecting any of the checkboxes first.. Oh shit it works... the values are null, but they should be since nothing is selected.. Ok lets try changing the <apex:selectCheckboxes> to <apex:selectList> and see if they work.. damn, it seems to work, the resultsString is getting updated, but the values are still showing as null in the logs so I guess its not working, but atleast its working better than the <apex:selectCheckboxes>.. 

It shouldn't be anything to do with the maps or repeat but why dont we do this the long way and see if that helps..

<apex:page showHeader="true" sidebar="true" controller="sfg_testBugWithActionButton">
<apex:form>
    <apex:outputpanel id="mainWrap">
      <div class="filterItem">
        <h2>Grade</h2>
        <apex:selectCheckboxes value="{!fGrade}" layout="pageDirection">
          <apex:selectOptions value="{!soGrade}" />
        </apex:selectCheckboxes>
      </div>
      <div class="filterItem">
        <h2>Gender</h2>
        <apex:selectCheckboxes value="{!fGender}" layout="pageDirection">
          <apex:selectOptions value="{!soGender}" />
        </apex:selectCheckboxes>
      </div>
      <div class="filterItem">
        <h2>Role</h2>
        <apex:selectCheckboxes value="{!fRole}" layout="pageDirection">
          <apex:selectOptions value="{!soRole}" />
        </apex:selectCheckboxes>
      </div>
      <apex:commandButton action="{!preformAction}" rerender="renderWrap" value="Submit Action" />
      <apex:outputpanel id="renderWrap">
        {!resultString}
      </apex:outputpanel>
    </apex:outputpanel>
  </apex:form>
</apex:page>


public class sfg_testBugWithActionButton {
  public String fGender {get; set;}
  public String fGrade {get; set;}
  public String fRole {get; set;}
  public List<SelectOption> soGender {get; set;}
  public List<SelectOption> soGrade {get; set;}
  public List<SelectOption> soRole {get; set;}
  public String resultString {get; set;}

  public sfg_testBugWithActionButton() {
    createfilterMap();
    resultString = 'on Load of page';
  }

  public PageReference preformAction() {
    system.debug('Gender: ' + fGender);
    system.debug('Grade: ' + fGrade);
    system.debug('Role: ' + fRole);
    resultString = 'button action preformed';
    return null;
  }

  private void createfilterMap() {
    soGender = new List<SelectOption>();
    soGender.add(new SelectOption('Male', 'Male'));
    soGender.add(new SelectOption('Female', 'Female'));
      
    soGrade = new List<SelectOption>();
    soGrade.add(new SelectOption('A', 'A'));
    soGrade.add(new SelectOption('B', 'B'));
    soGrade.add(new SelectOption('C', 'C'));
    
    soRole = new List<SelectOption>();
    soRole.add(new SelectOption('Support', 'Support'));
    soRole.add(new SelectOption('Sales', 'Sales'));
    soRole.add(new SelectOption('Marketing', 'Marketing'));
  }
}

No joy, same problem with or without the repeat when using <apex:selectCheckboxes>. Now lets try the same thing but with <apex:selectList> instead.. And it works! But why wouldn't it work when in the repeat or with the <apex:selectCheckboxes>? I also made sure it works when I added the attribute multiselect="true" to them, since that is what I would need anyhow if I had to use Lists over checkboxes.

So lets review: 
Right now it seems that anything with the <apex:selectCheckboxes> wont work (regardless of version since I did try bumping it back on both the page and controller). When trying to be dynamic and using a repeat to populate the Lists it fails as well for both <apex:selectCheckboxes> and <apex:selectList>. But if you do it the long way, you can get <apex:selectList> to work at the very least, although that means more fighting with style to get it to look like checkboxes if that is the desired effect. 

*sad panda*

I posted a simpler test of this not working here: Stackoverflow

**Update**
ANSWER FOUND and so not a bug lol bellow is the update made to the controller, and no update was needed to the page(see page at top of post for code):


public class sfg_testBugWithActionButton {

  public List<String> fGender {get; set;}
  public List<String> fGrade {get; set;}
  public List<String> fRole {get; set;}

  public String resultString {get; set;}

  public Map<String, List<SelectOption>> filterMap {get; set;}

  //this had to changed from Map<String, String> to what you see below
  public Map<String, List<String>> filterKeys {get; set;}

  public sfg_testBugWithActionButton() {
    //this was also added, and helped make it all work
    fGender = new List<String>();
    fGrade = new List<String>();
    fRole = new List<String>();

    // and so this got updated as well to use the param and not a string
    filterKeys = new Map<String, List<String>> {
      'Gender' => fGender,
      'Grade' => fGrade,
      'Role' => fRole
    };
    createfilterMap();
    resultString = 'on Load of page';
  }

  public PageReference preformAction() {
    resultString = 'button action preformed';
    return null;
  }

  private void createfilterMap() {
    filterMap = new Map<String, List<SelectOption>>();
    List<SelectOption> options = new List<SelectOption>();

    for (String s : filterKeys.keySet()) {
      if (s == 'Gender') {
        options = new List<SelectOption>();
        options.add(new SelectOption('Male', 'Male'));
        options.add(new SelectOption('Female', 'Female'));
        filterMap.put('Gender', options);
      }
      if (s == 'Grade') {
        options = new List<SelectOption>();
        options.add(new SelectOption('A', 'A'));
        options.add(new SelectOption('B', 'B'));
        options.add(new SelectOption('C', 'C'));
        filterMap.put('Grade', options);
      }
      if (s == 'Role') {
        options = new List<SelectOption>();
        options.add(new SelectOption('Support', 'Support'));
        options.add(new SelectOption('Sales', 'Sales'));
        options.add(new SelectOption('Marketing', 'Marketing'));
        filterMap.put('Role', options);
      } 
    }
  }
}


HAZA! :-)