This subsection provides the code for a sample implementation of the Skills Matrix. It includes the controller, the page, and controller test cases.
Listing 6.20 contains a sample implementation of the Skills Matrix controller class. The controller has four variables, each with a getter method for access by the Visualforce page. The selectedContactId
variable contains the unique identifier of the contact selected for editing or viewing. isEditable
is a flag used by the page to enable or disable the Save button and to determine whether to render skills as text fields or editable drop-down lists. The selectedContact
variable contains several fields from the Contact object needed throughout the controller, queried using the selectedContactId
. The selectedSkills
list contains the skill types and ratings to be displayed and edited in the user interface, and this same list is used to update the database upon a save action.
The controller has two actions: save
and refresh
. The save
action applies the changes from the drop-down lists of skill ratings by upserting them into the database. The refresh
action uses the unique identifier of the currently selected contact (selectedContactId
) to query the database for Skill records. It compares them against the complete list of skill types via the database metadata call getPicklistValues
. Finally, it updates the isEditable
variable based on whether the current user is privileged or is associated with the currently viewed contact.
Several helper methods are in the controller. addError
and addInfo
are shortcuts for adding notifications to the page, displayed using the pageMessages
component. The getCurrentUserContact
method queries the Contact record corresponding to the current user. The isManager
method returns true
if the user is privileged, enabling the user to edit the skills of any contact.
public class SkillsMatrixController {
public String selectedContactId { get; set; }
public Boolean isEditable { get; private set; }
public Contact selectedContact { get; private set; }
public List<Skill__c> selectedSkills { get; private set; }
public List<SelectOption> getContactOptions() {
List<SelectOption> options = new List<SelectOption>();
options.add(new SelectOption(
'', '-- Select Contact --'));
List<Contact> contacts = [ SELECT Id, Name
FROM Contact ORDER BY LastName ];
for (Contact contact : contacts) {
options.add(new SelectOption(contact.Id,
contact.Name));
}
return options;
}
public PageReference refresh() {
if (selectedContactId == null) {
addError('Select a contact'),
return null;
}
selectedContact = [ SELECT Id, Name,
User__r.UserRoleId,
User__r.ProfileId,
(SELECT Type__c, Rating__c, LastModifiedDate
FROM Skills__r ORDER BY Rating__c DESC)
FROM Contact
WHERE Id = :selectedContactId
LIMIT 1 ];
Set<String> skillTypes = new Set<String>();
selectedSkills = new List<Skill__c>();
for (Skill__c skill : selectedContact.Skills__r) {
skillTypes.add(skill.Type__c);
selectedSkills.add(skill);
}
Schema.DescribeFieldResult field = Skill__c.Type__c.getDescribe();
String picklistValue = null;
for (Schema.PicklistEntry entry : field.getPicklistValues()) {
picklistValue = entry.getLabel();
if (!skillTypes.contains(picklistValue)) {
selectedSkills.add(
new Skill__c(Contact__c = selectedContact.Id,
Type__c = picklistValue));
}
}
if (isManager()) {
isEditable = true;
} else {
Contact userContact = getCurrentUserContact();
isEditable =
selectedContact != null && userContact != null
&& selectedContact.Id == userContact.Id;
}
return null;
}
private void addError(String msg) {
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.ERROR, msg));
}
private void addInfo(String msg) {
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.INFO, msg));
}
public Contact getCurrentUserContact() {
List<Contact> userContact = [ SELECT Id, Name,
User__r.UserRoleId, User__r.ProfileId
FROM Contact
WHERE User__c = :UserInfo.getUserId()
LIMIT 1 ];
if (userContact.size() == 0) {
addError('No contact associated with user'),
return null;
} else {
return userContact.get(0);
}
}
private Boolean isManager() {
List<Profile> profiles = [ SELECT Id
FROM Profile WHERE Name IN (
'Project Manager', 'Vice President', 'System Administrator')
AND Id = :UserInfo.getProfileId() LIMIT 1 ];
return profiles.size() == 1;
}
public PageReference save() {
try {
upsert selectedSkills;
addInfo('Changes saved'),
} catch(DmlException e) {
addError('Could not save changes: ' + e.getMessage());
}
return null;
}
}
Listing 6.21 contains sample code for the Skills Matrix Visualforce page. It uses Force.com-styled view components to achieve an appearance that resembles the native user interface. The pageBlock
and pageBlockButtons
components visually separate the selection of the resource from the skills data and Save button, and the sectionHeader
component mimics the appearance of a native object tab.
The pageBlockTable
component iterates over the list of skills, displaying them as a table using standard Force.com styling. Each row of the table includes two columns. The first column contains the skill type. The second contains two components: one for editing the skill rating and another strictly for viewing it. Only one of these components is shown at a time. They are rendered conditionally based on whether the controller has determined the data to be editable. If the skills data is editable, only the inputField
component is rendered. If the current user does not have the rights to edit the ratings, only the outputField
is rendered.
<apex:page controller="SkillsMatrixController"
tabStyle="Skill__c">
<style>
.contactLabel { padding-right: 15px; }
.goButton { margin-left: 10px; }
</style>
<apex:sectionHeader title="Services Manager"
subtitle="Skills Matrix" />
<apex:pageMessages />
<apex:form id="form">
<apex:outputLabel value="Contact:" for="selectedContactId"
styleClass="contactLabel" />
<apex:selectList id="selectedContactId" title="Contact"
value="{!selectedContactId}" size="1">
<apex:selectOptions value="{!contactOptions}" />
</apex:selectList>
<apex:commandButton action="{!refresh}" value="Go!"
styleClass="goButton" />
<p />
<apex:pageBlock title="Skills">
<apex:pageBlockButtons>
<apex:commandButton action="{!save}" value="Save"
disabled="{!NOT isEditable}" />
</apex:pageBlockButtons>
<apex:pageBlockTable value="{!selectedSkills}" var="skill"
rendered="{!selectedContactId != ''}">
<apex:column value="{!skill.Type__c}" />
<apex:column headerValue="Rating">
<apex:outputField value="{!skill.Rating__c}"
rendered="{!NOT isEditable}" />
<apex:inputField value="{!skill.Rating__c}"
rendered="{!isEditable}" />
</apex:column>
<apex:column value="{!skill.LastModifiedDate}" />
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
The test cases in Listing 6.22 achieve 96% coverage of the Skills Matrix controller. They begin with a static initializer and init
method to prepare the database for the tests by adding test data. This data is not permanent. All database actions during testing are rolled back automatically upon test completion.
The test cases rely on two Contact records: Tim and Barry. To test the behavior of the Skills Matrix on existing data, Tim is given a single Skill record, whereas Barry is left without skills. For testing security, Tim’s Contact record is associated with a User record named Tim, whereas Barry’s Contact record is not mapped to a User record. Update the query for the users in the static initializer to match two usernames in your own organization.
@isTest
private class TestSkillsMatrixController {
static PageReference page;
static SkillsMatrixController controller;
static Contact barry, tim;
static User barryUser, timUser;
static {
timUser = [ SELECT Id FROM User WHERE Name = 'Tim Barr' LIMIT 1 ];
barryUser = [ SELECT Id FROM User WHERE Name = 'Barry Cade' LIMIT 1 ];
init();
}
private static void init() {
barry = new Contact(FirstName = 'Barry', LastName = 'Cade'),
tim = new Contact(FirstName = 'Tim', LastName = 'Barr',
User__c = timUser.Id);
insert new Contact[] { barry, tim };
Skill__c[] skills = new Skill__c[] {
new Skill__c(Type__c = 'Java', Rating__c = '3',
Contact__c = tim.Id) };
insert skills;
page = new PageReference('SkillsMatrix'),
Test.setCurrentPage(page);
controller = new SkillsMatrixController();
}
static testMethod void testAsUser() {
System.runAs(timUser) {
init();
controller.selectedContactId = barry.Id;
controller.refresh();
System.assert(!controller.isEditable);
controller.selectedContactId = tim.Id;
controller.refresh();
System.assert(controller.isEditable);
}
}
static testMethod void testNoContactForUser() {
System.runAs(barryUser) {
init();
controller.selectedContactId = barry.Id;
controller.refresh();
System.assert(ApexPages.hasMessages(ApexPages.Severity.ERROR));
}
}
static testMethod void testNoSkills() {
controller.getContactOptions();
controller.selectedContactId = barry.Id;
controller.refresh();
System.assert(controller.selectedSkills.size() > 0);
System.assert(controller.isEditable);
}
static testMethod void testWithSkills() {
controller.getContactOptions();
controller.selectedContactId = tim.Id;
controller.refresh();
System.assert(controller.selectedSkills.size() > 0);
System.assert(controller.selectedSkills.get(0).Type__c == 'Java'),
}
static testMethod void testNoContactSelected() {
controller.selectedContactId = null;
PageReference ref = controller.refresh();
System.assert(ApexPages.hasMessages());
}
static testMethod void testSave() {
final String skillRating = '5 - Expert';
controller.getContactOptions();
controller.selectedContactId = barry.Id;
controller.refresh();
List<Skill__c> selectedSkills = controller.selectedSkills;
Skill__c skill = selectedSkills.get(0);
skill.Rating__c = skillRating;
String skillType = skill.Type__c;
controller.save();
System.assert(ApexPages.hasMessages(ApexPages.Severity.INFO));
Skill__c savedSkill = [ SELECT Rating__c FROM Skill__c
WHERE Contact__c = :barry.Id AND
Type__c = :skillType LIMIT 1 ];
System.assert(savedSkill != null &&
savedSkill.Rating__c == skillRating);
}
}
The test methods are described here in the order in which they appear in the code:
testAsUser—This test uses the System.runAs
method to assume the identity of Tim. Tim is assigned to a User, so when his corresponding Contact record is selected and the list of skills is refreshed, the isEditable
flag should be set to true
. If Barry is selected, the flag should be false
.
testNoContactForUser—System.runAs
is used again, this time to test for an error condition. Barry’s user does not have a child Contact record, so he should receive an error when visiting the Skills Matrix. Without a mapping to the User object, the application cannot determine whether the current user has access to edit skills.
testNoSkills—This test method runs as a System Administrator. It selects Barry from the contact list and refreshes, asserting that there are Skills records. These records are created from the Skill object’s Type__c
field’s picklist values. Another assertion is made that the skill ratings are editable because an administrator can edit the skills of all contacts.
testWithSkills—This test retrieves the skills for Tim and asserts that the Java skill is first in the list. This is because Tim already has a Skill record for Java, and existing records should be placed at the top of the user interface.
testNoContactSelected—The selected contact is set to null to verify that an information message is added to the page. This message instructs the user to select a contact.
testSave—This test uses the controller to rate Barry as an expert in the first skill on the skills list. It then queries the database independently to verify that the controller saved the data correctly.
3.143.237.136