Teach Me Salesforce

A community approach to learning salesforce.com

Integrating Google Maps in Salesforce

with one comment

Requirement is to show the location of my Account on Google Maps. It looks difficult but actually it is very simple to implement.

Just create a new visualforce page with this code

<apex:page standardController="Account">

<script src="http://maps.google.com/maps?file=api">
</script>

<script type="text/javascript">

var map = null;
var geocoder = null;

var address = "{!Account.BillingStreet}, {!Account.BillingPostalCode} {!Account.BillingCity}, {!Account.BillingState}, {!Account.BillingCountry}";

function initialize() {
if(GBrowserIsCompatible())
{
map = new GMap2(document.getElementById("MyMap"));
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl3D());

geocoder = new GClientGeocoder();
geocoder.getLatLng(
address,
function(point) {
if (!point) {
document.getElementById("MyMap").innerHTML = address + " not found";
} else {
map.setCenter(point, 13);
var marker = new GMarker(point);
map.addOverlay(marker);
marker.bindInfoWindowHtml("Account Name : <b><i> {!Account.Name} </i></b>
Address : "+address);
}
}
);
}
}
</script>
<div id="MyMap" style="width:100%;height:300px"></div>
<script>
initialize() ;
</script>

</apex:page>

As you can see that I have used standard controller so you need to pass the account Id in URL. Let’s say if the page name is “GoogleMaps” then the URL will look something like this : “…/apex/GoogleMaps?id=YOUR_ACCOUNT_ID”.

Use this page as in line visualforce page on native layout and enjoy the Google maps with Salesforce.

References

http://code.google.com/apis/maps/documentation/webservices/

http://code.google.com/apis/maps/index.html

 

Original Post by Forceguru : http://forceguru.blogspot.com/2012/01/integrating-google-maps-in-salesforce.html

Written by Ankit Arora (forceguru)

January 16, 2012 at 3:18 am

Displaying Role Hierarchy on Visualforce Page – Tree View

leave a comment »

This is a cross post from my Blog.

Ever thought of building a tree like data structure for the users based on role hierarchy and displaying it in the form of a JavaScript tree with node selection capability on the Visualforce page?

So recently I came across a functionality where a third party javascript calendar was used on the VisualForce page and all events were fetched programmatically through Apex and plotted on the calendar. The UI looked good along with other custom developed functionality. All was fine until client asked if it was possible to select some of the logged in user’s subordinates through custom VF page and plot events on the calendar for the selected users only. In other words, fetch and display only those events which were owned by users who worked below the logged in user in the role hierarchy.

It got me thinking and I did some research to check if there was an easier way to get this done, but soon realized that this required custom and tricky Apex/VF code. There’s a nice little script written by Jeff Douglas that was closest to what I actually wanted.

So I came up with this handy utility which fulfils my requirements. Getting user IDs of subordinates could also be useful in situations where, for example, you want to do a comparative analysis of performance for all users reporting to a manager.

There are mainly two parts to the solution I designed:

1. RoleUtil (Apex Class): Utility class which exposes the following API

a. public static RoleNodeWrapper getRootNodeOfUserTree (Id userOrRoleId) – function creates the tree data structure for the requested user or role ID and returns the root node to the caller

b. public static ListgetAllSubordinates (Id userId) – function returns the list of all subordinate users for the requested user ID

c. public static String getTreeJSON (Id userOrRoleId) – function returns the JSON string for the requested user or role ID

d. public static String getSObjectTypeById(Id objectId) – general utility function to return the string representation of the object type for the requested object ID

e. public static Boolean isRole (Id objId) – internally uses the getSObjectTypeById (#d above) to check whether the requested object ID is of UserRole type

f. public class RoleNodeWrapper (inner class) – wrapper for user role, represents a node in the tree data structure mentioned above and exposes boolean properties like hasChildren, hasUsers, isLeafNode, etc

public class RoleUtil {

    /********************* Properties used by getRootNodeOfUserTree function - starts **********************/
    // map to hold roles with Id as the key
    private static Map  roleUsersMap;

    // map to hold child roles with parentRoleId as the key
    private static Map > parentChildRoleMap;

    // List holds all subordinates
    private static List allSubordinates {get; set;}

    // Global JSON generator
    private static JSONGenerator gen {get; set;}

    /********************* Properties used by getRootNodeOfUserTree function - ends **********************/

    /********************* Properties used by getSObjectTypeById function - starts ********************* */
    // map to hold global describe data
    private static Map gd;

    // map to store objects and their prefixes
    private static Map keyPrefixMap;

    // to hold set of all sObject prefixes
    private static Set keyPrefixSet;
    /********************* Properties used by getSObjectTypeById function - ends **********************/

    /* // initialize helper data */ 
    static {
        // initialize helper data for getSObjectTypeById function
        init1();

        // initialize helper data for getRootNodeOfUserTree function
        init2();
    }

    /* // init1 starts  */
    private static void init1() {
        // get all objects from the org
        gd = Schema.getGlobalDescribe();

        // to store objects and their prefixes
        keyPrefixMap = new Map{};

        //get the object prefix in IDs
        keyPrefixSet = gd.keySet();

        // fill up the prefixes map
        for(String sObj : keyPrefixSet) {
            Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
            String tempName = r.getName();
            String tempPrefix = r.getKeyPrefix();
            keyPrefixMap.put(tempPrefix, tempName);
        }
    }
    /* // init1 ends */

    /* // init2 starts  */
    private static void init2() {

        // Create a blank list
        allSubordinates = new List();

        // Get role to users mapping in a map with key as role id
        roleUsersMap = new Map([select Id, Name, parentRoleId, (select id, name from users) from UserRole order by parentRoleId]);

        // populate parent role - child roles map
        parentChildRoleMap = new Map >();        
        for (UserRole r : roleUsersMap.values()) {
            List tempList;
            if (!parentChildRoleMap.containsKey(r.parentRoleId)){
                tempList = new List();
                tempList.Add(r);
                parentChildRoleMap.put(r.parentRoleId, tempList);
            }
            else {
                tempList = (List)parentChildRoleMap.get(r.parentRoleId);
                tempList.add(r);
                parentChildRoleMap.put(r.parentRoleId, tempList);
            }
        }
    } 
    /* // init2 ends */

    /* // public method to get the starting node of the RoleTree along with user list */
    public static RoleNodeWrapper getRootNodeOfUserTree (Id userOrRoleId) {
        return createNode(userOrRoleId);
    }

    /* // createNode starts */
    private static RoleNodeWrapper createNode(Id objId) {
        RoleNodeWrapper n = new RoleNodeWrapper();
        Id roleId;
        if (isRole(objId)) {
            roleId = objId;
            if (!roleUsersMap.get(roleId).Users.isEmpty()) {
                n.myUsers = roleUsersMap.get(roleId).Users;
                allSubordinates.addAll(n.myUsers);
                n.hasUsers = true;
            }
        }
        else {
            List tempUsrList = new List();
            User tempUser = [Select Id, Name, UserRoleId from User where Id =: objId];
            tempUsrList.add(tempUser);
            n.myUsers = tempUsrList;
            roleId = tempUser.UserRoleId;
        }
        n.myRoleId = roleId;
        n.myRoleName = roleUsersMap.get(roleId).Name;
        n.myParentRoleId = roleUsersMap.get(roleId).ParentRoleId;

        if (parentChildRoleMap.containsKey(roleId)){
            n.hasChildren = true;
            n.isLeafNode = false;
            List lst = new List();
            for (UserRole r : parentChildRoleMap.get(roleId)) {
                lst.add(createNode(r.Id));
            }           
            n.myChildNodes = lst;
        }
        else {
            n.isLeafNode = true;
            n.hasChildren = false;
        }
        return n;
    }

    public static List getAllSubordinates(Id userId){
        createNode(userId);
        return allSubordinates;
    }

    public static String getTreeJSON(Id userOrRoleId) {
        gen = JSON.createGenerator(true);
        RoleNodeWrapper node = createNode(userOrRoleId);
        gen.writeStartArray();
            convertNodeToJSON(node);
        gen.writeEndArray();
        return gen.getAsString();
    }

    private static void convertNodeToJSON(RoleNodeWrapper objRNW){
        gen.writeStartObject();
            gen.writeStringField('title', objRNW.myRoleName);
            gen.writeStringField('key', objRNW.myRoleId);
            gen.writeBooleanField('unselectable', false);
            gen.writeBooleanField('expand', true);
            gen.writeBooleanField('isFolder', true);
            if (objRNW.hasUsers || objRNW.hasChildren)
            {
                gen.writeFieldName('children');
                gen.writeStartArray();
                    if (objRNW.hasUsers)
                    {
                        for (User u : objRNW.myUsers)
                        {
                            gen.writeStartObject();
                                gen.writeStringField('title', u.Name);
                                gen.writeStringField('key', u.Id);
                            gen.WriteEndObject();
                        }
                    }
                    if (objRNW.hasChildren)
                    {
                        for (RoleNodeWrapper r : objRNW.myChildNodes)
                        {
                            convertNodeToJSON(r);
                        }
                    }
                gen.writeEndArray();
            }
        gen.writeEndObject();
    }

    /* // general utility function to get the SObjectType of the Id passed as the argument, to be used in conjunction with */ 
    public static String getSObjectTypeById(Id objectId) {
        String tPrefix = objectId;
        tPrefix = tPrefix.subString(0,3);

        //get the object type now
        String objectType = keyPrefixMap.get(tPrefix);
        return objectType;
    }
    /* // utility function getSObjectTypeById ends */

    /* // check the object type of objId using the utility function getSObjectTypeById and return 'true' if it's of Role type */
    public static Boolean isRole (Id objId) {
        if (getSObjectTypeById(objId) == String.valueOf(UserRole.sObjectType)) {
            return true;
        }
        else if (getSObjectTypeById(objId) == String.valueOf(User.sObjectType)) {
            return false;
        } 
        return false;
    }
    /* // isRole ends */

    public class RoleNodeWrapper {

        // Role info properties - begin
        public String myRoleName {get; set;}

        public Id myRoleId {get; set;}

        public String myParentRoleId {get; set;}
        // Role info properties - end

        // Node children identifier properties - begin
        public Boolean hasChildren {get; set;}

        public Boolean isLeafNode {get; set;}

        public Boolean hasUsers {get; set;}
        // Node children identifier properties - end

        // Node children properties - begin
        public List myUsers {get; set;}

        public List myChildNodes {get; set;}
        // Node children properties - end   

        public RoleNodeWrapper(){
            hasUsers = false;
            hasChildren = false;
        }
    }
 }

2. TreeView (Visualforce component): Dynatree based reusable VF component that exposes input parameters like

a. roleOrUserId – required string type input attribute

b. selectable – boolean attribute to indicate whether you want to display checkboxes against nodes in the tree for user selection

c. JsonData – optional string type input attribute, if supplied to the component ignores the “roleOrUserId” attribute and displays the tree structure for the input JSON string

d. value – a string type output attribute which returns the IDs/Keys of the selected nodes in the CSV format, which can then be utilised by the page controller

<apex:component controller="TreeViewController">
<apex:attribute name="roleOrUserId" required="true" type="String" assignTo="{!roleOrUserId}" description="Enter Role or User Id to build the hierarchy. Pass null if you are passing JSON data as a parameter" />
<apex:attribute name="selectable" type="Boolean" assignTo="{!selectable}" description="Do you want nodes to be selectable?" />
<apex:attribute name="value" type="String" description="IDs of selected Nodes in CSV format" />
<apex:attribute name="JsonData" type="String" assignTo="{!JsonData}" description="JSON input for the tree component" />
<apex:inputHidden id="selectedKeys" value="{!value}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery-ui.custom.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery.cookie.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'src/jquery.dynatree.js' )}" />

<apex:stylesheet value="{!URLFOR($Resource.DynaTree, 'src/skin/ui.dynatree.css')}" />

<!-- Add code to initialize the tree when the document is loaded: -->
<script type="text/javascript">
$(function(){
// Attach the dynatree widget to an existing <div id="tree"> element
// and pass the tree options as an argument to the dynatree() function:
$("#tree").dynatree({
onActivate: function(node) {
// A DynaTreeNode object is passed to the activation handler
// Note: we also get this event, if persistence is on, and the page is reloaded.
//alert("You activated " + node.data.key);
},
persist: false,
checkbox: {!selectable},
generateIds: false,
classNames: {
checkbox: "dynatree-checkbox",
expanded: "dynatree-expanded"
},
selectMode: 3,
children: {!JsonString},
onSelect: function(select, node) {
// Get a list of all selected nodes, and convert to a key array:
var selKeys = $.map(node.tree.getSelectedNodes(), function(node){
return node.data.key;
});
jQuery(document.getElementById("{!$Component.selectedKeys}")).val(selKeys.join(", "));

// Get a list of all selected TOP nodes
var selRootNodes = node.tree.getSelectedNodes(true);
// ... and convert to a key array:
var selRootKeys = $.map(selRootNodes, function(node){
return node.data.key;
});
},
});
});
</script>

<!-- Add a <div> element where the tree should appear: -->
<div id="tree"> </div>

</apex:component>

You can see a working demo of the functionality here: http://treeview-developer-edition.ap1.force.com/

The code is available as unmanaged package (https://login.salesforce.com/packaging/installPackage.apexp?p0=04t90000000LlqQ) if you want to use it in your org. The code has been written assuming positive use cases and exceptional situations have not much been handled. It is advised to review and tweak the code before you use it in your org.

Written by Ankit Arora (forceguru)

January 16, 2012 at 2:59 am

Apex – Sorting a map

leave a comment »

So this is going to be one of the more apex heavy posts. This is a challenge I think many developers
have come across, and while what I propose is by no means the most elegant thing ever, it does do the
job until hopefully salesforce implements a native map sorting method. So here is the basic approach

1) populate map with information
2) create another map with the keys as the values you want to sort by, and the value as the key
for the other map.
3) create a list whos values are the keyset of the map created in step 2
4) sort the list
5) iterate over the list which now has your values in order, get the value from map 2 using the
current loop iterator as the key.

Sounds pretty complicated eh? It’s not SO bad once you kinda get the hang of it, but there is one
gotcha that kind of sucks, but we’ll cover that in a minute.

Here is a sample using a simple object called contestEntry. We’ll create a bunch of them with
random ordering, and then sort them and loop over the sorted result. You should be able to run this
in any org so you can see the principals in action.

        public class contestEntry
        {
            public decimal rank{get;set;}
            public string name{get;set;}
        }

        map&lt;string,contestEntry&gt; entries = new map&lt;string,contestEntry&gt;();

        contestEntry entry1 = new contestEntry();
        entry1.rank = 5;
        entry1.name = 'Frank';
        entries.put(entry1.name,entry1);

        contestEntry entry2 = new contestEntry();
        entry2.rank = 3;
        entry2.name = 'Bob';
        entries.put(entry2.name,entry2);

        contestEntry entry3 = new contestEntry();
        entry3.rank = 1;
        entry3.name = 'Jones';
        entries.put(entry3.name,entry3);

        contestEntry entry4 = new contestEntry();
        entry4.rank = 4;
        entry4.name = 'Sandy';
        entries.put(entry4.name,entry4);

        contestEntry entry5 = new contestEntry();
        entry5.rank = 2;
        entry5.name = 'Felix';
        entries.put(entry5.name,entry5);

        //oh no, these entries are all out of order.
        system.debug(entries) ;

        //lets get sorting these guys. First we'll need a map to store the rank, and the contestEntry that rank is
        //associated with
        map&lt;decimal,string&gt; rankToNameMap = new map&lt;decimal,string&gt;();
        for(contestEntry entry : entries.values())
        {
            rankToNameMap.put(entry.rank,entry.name);
        }
        //now lets put those ranks in a list
        list&lt;decimal&gt; ranksList = new list&lt;decimal&gt;();
        ranksList.addAll(rankToNameMap.keySet());

        //now sort them
        ranksList.sort();

        //ok, so now we have the ranks in order, we need to figure out who had that rank
        for(decimal rank : ranksList)
        {
            String thisEntryName = rankToNameMap.get(rank);
            contestEntry thisEntry = entries.get(thisEntryName);
            system.debug(thisEntry);
        }

Walking through it first we just make a sample object to use here. Normally this would be whatever you are actually trying to sort, but for the sake of easyness I just created an object called contestEntry. It just holds a name and a rank.

So then I make a map of those things, keyed by the persona name, and containing the contestEntry object. You might in real life have a map of sObjects keyed by their Id and containing the sObject itself. So then I make a bunch of those and add them to the map in random order so my sorting actually has some work to do :P

The next thing is creating a map with the key of the value I want to sort this list by. The value is key of the first map. So in real life this might be a dollar amount on an opportunity and then the opportunity Id if the original map was a list of opportunities keyed by their Id.

We loop over all the objects in the original map, and add them to our temporary sorting map, again keyed by value to sort by, and value with key from original map.

Then we create a list of the type of the key of the temporary sorting map. Your key was a decimal? Then your list is of decimals as well, etc. Then add all the keys from the sorting map to the list you just made.

Sort the list.

Iterate over the sorted list, each entry in this list will be a key you can use to get the entry from the sorting map, which will contain the key to the original map. You now have a reference to your original map value by whatever value you sorted on.

There is however one gotcha with this approach. If you have duplicates in the value you are going to sort by, you are going to end up with collisions in your sorting map, which will end up with values overwriting each other and ultimately values missing in your final iteration. This happens because say in my example I have two people with rank 1. First person comes through, their rank is 1, and their name is Sandy. So the sorting map has 1=sandy. Then another person comes through, they also have rank 1 and their name is jones. Now the map has 1=jones. Sandy just fell out of the list. How do you deal with this? The best hack-ish fix I could come up with is to see if the key you are attempting to write to already exists, if so, then write to a slightly higher value key. Basically replace

       for(contestEntry entry : entries.values())
        {
            rankToNameMap.put(entry.rank,entry.name);
        }

with

for(contestEntry entry : entries.values())
        {
                decimal rank = entry.rank;
                while(rankToNameMap.containsKey(rank))
                {
                    system.debug('------ INCRIMENTING Rank TOTAL FOR ' + entry.name);
                    rank += 0.001;
                }

                rankToNameMap.put(rank,entry.name);
        }

This won’t affect the value displayed when you retrieve and display the object, it just changes the sort order. I haven’t tested this throughly though so don’t rely on it extensively.

Anyway, there you have it Apex fans. A method to reliable (if not quickly or efficiently) sort Apex maps by just about anything. Once you understand the concepts it’s fairly easy to expand to sort to your hearts desire.
Originally posted by Kenji776 @ http://iwritecrappycode.wordpress.com/2011/09/28/apex-sorting-a-map/

Written by kenji776

September 28, 2011 at 5:07 pm

Mastering the Force.com IDE: Searching metadata

with 10 comments

The Force.com IDE is a developer application that is great utility for Salesforce.com administrators even if they never have to write or troubleshoot Apex or Visualforce. The most useful capability for any admin is the Search feature which allows you to find all the places a field is used: workflow, reports, templates, formulas, etc.

Let’s consider this scenario: you’ve been hired to update a company’s Salesforce org and you discover a duplicate field on Accounts. You run reports and find out both fields are populated with data. Before you can decide which one to keep and which to delete, you need to know all the locations in which each field is used: reports, views, workflow, templates, etc. You can find all those configurations in minutes with your Salesforce org’s metadata in a project in the Force.com IDE.

I’ll assume you’ve already installed Eclipse and the Force.com IDE. If you haven’t, developer.force.com has a great page to help you; see you back here soon!

The first step is to create a Force.com project that contains all your metadata. Do this during the set up by selecting all the Metadata components.

New Force.com Project Wizard: Choose Initial Project Contents

New Force.com Project Wizard: Choose Metadata Components

If you have a very large amount of metadata and Eclipse throws a Java Out of Memory error, you can either apply this solution or choose fewer metadata components. You should at least include the following to ensure you are searching the common components that reference fields.

Component file extension
Apex Classes
Visualforce Components
Email Templates
Page Layout
Custom Objects
Standard Objects
Object Translations
Visualforce Pages
Profiles
Reports(includes reports built from Custom Report Types)
S-controls
Apex Triggers
Workflow
.cls
.component
.email
.layout
.object
.object
.translation
.page
.profile
.report
.scontrols
.trigger
.workflow

Once you have created your project, you’re ready to start your search!

  1. Click on your Project to highlight it
  2. Click on the Search menu in the menu bar and select Search… to open the Search dialog box
  3. Enter the API Name of the field you’re seeking in Containing Text
  4. Copy the following file extension names into the field called File Name Patterns:
    *.cls, *.component, *.email, *.layout, *.object, *.page, *.profile, *.report, *.scontrols, *.trigger, *.workflow
  5. Set the Scope option to “Selected Resource” to ensure that only one Project is being searched.
  6. Click Search
  7. The Search Results pane will open when your search is completed with a list of all locations where the field occurs.

The following video demonstrates these steps in the Force.com IDE for Windows (Mac version is similar)

The search results will show you a tree with all the results in each metadata component. Click on any result will open the metadata file and show you exactly where your field occurs. You’ll be viewing XML, but don’t worry if you’ve never looked at XML before, there’s nothing tricky here. In fact, the tags you see are very friendly. They’ll be “nested” which means indented like a outline so that you can tell which tags are subordinate to which other tags. With a little investigative analysis, you’ll be able to discern the exactly how a field is being used in the metadata.

For example, in this screenshot you can see the Search results pane showing the components in which the field was found. The Reports components are open and the report called “Server Alerts” has two matches. By clicking on the matches, the Server_Alerts.report XML file is opened and the matches are highlighted. In the XML you can see all the configurations for this particular report. The <columns> tags show which columns are displayed in the report’s output, so we can see that our “App Server” field is one of the output columns.  We also see the field nested in the <filter> tags and can translate the XML tags to tell us that the field is being used in a filter with the operator “notEqual” and no value which means we are looking for records where App Server is not populated with any data. You can compare it to the actual report in the UI which is depicted in the second image below.

Screenshot shows the Search Results panel for a metadata search and the XML for the field as it occurs in a custom report.
Here’s how this report looks in the declarative UI for comparison. Note how the fields selected in the report show up as <column> tags and the filter is divided up into <filter>, <criteriaItems>, <column> and <operator> tags.
Using the Force.com IDE for metadata searches is one of my favorite techniques for thorough database maintenance. Searching with the IDE is just a start. From the results, you can even update many components with a simple Find and Replace to replace the retired field with a new field…but we’ll save that for the next lesson in Mastering the Force.com IDE!

Written by Always Thinkin

September 25, 2011 at 2:48 pm

Salesforce Trigger when Rollups Summaries Not Possible

with 6 comments

Master-Details relationships in Force.com  are very handy but don’t fit every scenario. For instance, it’s not possible to implement a rollup summary on formula field or text fields. Here’s a small trigger that you can use for a starter for these types of situations. The code for each class is available at GitHub for your forking pleasure.

So here’s the (not very useful) use case. Sales Order is the Master object which can have multiple Sales Order Items (detail object). The Sales Order Item has a “primary” Boolean field and a “purchased country” field. Each time Sales Order Items are inserted or updated, if the Sales Order Item is marked as “primary” then the value of “purchased country” is written into the “primary country” field on the Sales Order. I’m assuming that there can only be one Sales Order Item per Sales Order that is marked as primary. Essentially this is just a quick reference on the Sales Order to see which country is primary on any of the multiple Sales Order Items. Not very useful but illustrative.

The code is broken down into a Trigger and an Apex “handler” class that implements the actual functionality. It’s best practice to only have one trigger for each object and to avoid complex logic in triggers. To simplify testing and resuse, triggers should delegate to Apex classes which contain the actual execution logic. See Mike Leach’s excellent trigger template for more info.

SalesOrderItemTrigger (source on GitHub) - Implements trigger functionality for Sales Order Items. Delegates responsibility to SalesOrderItemTriggerHandler.

trigger SalesOrderItemTrigger on Sales_Order_Item__c (after insert, after update) {

  SalesOrderItemTriggerHandler handler = new SalesOrderItemTriggerHandler();
    
  if(Trigger.isInsert && Trigger.isAfter) {
    handler.OnAfterInsert(Trigger.new);
    
  } else if(Trigger.isUpdate && Trigger.isAfter) { 
    handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap);
    
  }

}

SalesOrderItemTriggerHandler (source on GitHub) - Implements the functionality for the sales order item trigger after insert and after update. Looks at each sales order item and if it is marked as primary_item__c then moves the primary_country__c value from the sales order item to the associated sales order’s primary_country__c field.

public with sharing class SalesOrderItemTriggerHandler {

  // update the primary country when new records are inserted from trigger
  public void OnAfterInsert(List newRecords){
    updatePrimaryCountry(newRecords); 
  }
  
  // update the primary country when records are updated from trigger  
  public void OnAfterUpdate(List oldRecords, 
      List updatedRecords,  Map oldMap, 
      Map newMap){
    updatePrimaryCountry(updatedRecords); 
  }
  
  // updates the sales order with the primary purchased country for the item
  private void updatePrimaryCountry(List newRecords) {
    
    // create a new map to hold the sales order id / country values
    Map salesOrderCountryMap = new Map();
    
    // if an item is marked as primary, add the purchased country
    // to the map where the sales order id is the key 
    for (Sales_Order_Item__c soi : newRecords) {
      if (soi.Primary_Item__c)
        salesOrderCountryMap.put(soi.Sales_Order__c,soi.Purchased_Country__c);
    } 
    
    // query for the sale orders in the context to update
    List orders = [select id, Primary_Country__c from Sales_Order__c 
      where id IN :salesOrderCountryMap.keyset()];
    
    // add the primary country to the sales order. find it in the map
    // using the sales order's id as the key
    for (Sales_Order__c so : orders)
      so.Primary_Country__c = salesOrderCountryMap.get(so.id);
    
    // commit the records 
    update orders;
    
  }

}

Test_SalesOrderItemTriggerHandler (source on GitHub) - Test class for SalesOrderItemTrigger and SalesOrderItemTriggerHandler. Achieves 100% code coverage.

@isTest
private class Test_SalesOrderItemTriggerHandler {

  private static Sales_Order__c so1;
  private static Sales_Order__c so2;

  // set up our data for each test method
  static {
  	
  	Contact c = new Contact(firstname='test',lastname='test',email='no@email.com');
  	insert c;
    
    so1 = new Sales_Order__c(name='test1',Delivery_Name__c=c.id);
    so2 = new Sales_Order__c(name='test2',Delivery_Name__c=c.id);
    
    insert new List{so1,so2};
    
  }

  static testMethod void testNewRecords() {
 
    Sales_Order_Item__c soi1 = new Sales_Order_Item__c();
    soi1.Sales_Order__c = so1.id;
    soi1.Quantity__c = 1;
    soi1.Description__c = 'test';
    soi1.Purchased_Country__c = 'Germany';
 
    Sales_Order_Item__c soi2 = new Sales_Order_Item__c();
    soi2.Sales_Order__c = so1.id;
    soi2.Quantity__c = 1;
    soi2.Description__c = 'test';
    soi2.Purchased_Country__c = 'France';
    soi2.Primary_Item__c = true;
    
    Sales_Order_Item__c soi3 = new Sales_Order_Item__c();
    soi3.Sales_Order__c = so2.id;
    soi3.Quantity__c = 1;
    soi3.Description__c = 'test';
    soi3.Purchased_Country__c = 'Germany';
    soi3.Primary_Item__c = true;
     
    Sales_Order_Item__c soi4 = new Sales_Order_Item__c();
    soi4.Sales_Order__c = so2.id;
    soi4.Quantity__c = 1;
    soi4.Description__c = 'test';
    soi4.Purchased_Country__c = 'Germany';
    
    Sales_Order_Item__c soi5 = new Sales_Order_Item__c();
    soi5.Sales_Order__c = so2.id;
    soi5.Quantity__c = 1;
    soi5.Description__c = 'test';
    soi5.Purchased_Country__c = 'Italy';
            
    insert new List{soi1,soi2,soi3,soi4,soi5}; 
     
    System.assertEquals(2,[select count() from Sales_Order_Item__c where Sales_Order__c = :so1.id]);
    System.assertEquals(3,[select count() from Sales_Order_Item__c where Sales_Order__c = :so2.id]); 
    
    System.assertEquals('France',[select primary_country__c from Sales_Order__c where id = :so1.id].primary_country__c);
    System.assertEquals('Germany',[select primary_country__c from Sales_Order__c where id = :so2.id].primary_country__c);
 
  }
  
  static testMethod void testUpdatedRecords() {
    
    Sales_Order_Item__c soi1 = new Sales_Order_Item__c();
    soi1.Sales_Order__c = so1.id;
    soi1.Quantity__c = 1;
    soi1.Description__c = 'test';
    soi1.Purchased_Country__c = 'Germany';
 
    Sales_Order_Item__c soi2 = new Sales_Order_Item__c();
    soi2.Sales_Order__c = so1.id;
    soi2.Quantity__c = 1;
    soi2.Description__c = 'test';
    soi2.Purchased_Country__c = 'France';
    soi2.Primary_Item__c = true;
    
    insert new List{soi1,soi2}; 
    
    // assert that the country = France
    System.assertEquals('France',[select primary_country__c from Sales_Order__c where id = :so1.id].primary_country__c);
    
    List items = [select id, purchased_country__c from Sales_Order_Item__c 
      where Sales_Order__c = :so1.id and primary_item__c = true];
    // change the primary country on the sales order item. should trigger update
    items.get(0).purchased_country__c = 'Denmark';
   
    update items;
    // assert that the country was successfully changed to Denmark
    System.assertEquals('Denmark',[select primary_country__c from Sales_Order__c where id = :so1.id].primary_country__c);
 
  }
  
}

Written by Jeff Douglas

August 23, 2011 at 9:00 am

Posted in Apex, Code Sample, Trigger

Video Tutorial – Developing with Apex REST Services

with 3 comments

I’ve had this video queued up for a few days now as I was waiting for the Apex REST API webinar. I didn’t want to steal Sandeep Bhanot’s and Alex Toussaint’s thunder. Their webinar was very informative and packed full of code! It should be available here any day now for your viewing pleasure.

Over at CloudSpokes we’ve been running development challenges with Apex REST services for quite awhile now. To facilitate future challenges I put together a video showing everything (yes, everything) you need to know to get started building applications with Apex REST services. The tutorial below walks you through the entire process of building our Members service. It shows you how to structure your services, how to use the new REST classes, and how to use the Apigee Console for Salesforce.com to test your services. The Apigee console even handles the OAuth for you!! Why write your own Java or Rails client app to test your classes? I’ve even published the entire code for the Members service so you can use it as a template. You can find the code here at github.

So here’s how to get started:

  1. Sign up for a new developer org at developer.force.com. All new orgs are now enabled with the Apex REST services so you can get started right away without waiting for the feature to get enabled.
  2. Take a peek at the Force.com REST API site for tutorials, code snippets, webinars and documentation. You can even post questions to the message boards for quick answers to your problems.
  3. Watch this awesome video by our friends at Apigee entitled RESTful API Design: Teach a Dog to REST. We love the design principles.
  4. Watch the video above for a quick tutorial services and then check out the code.
  5. Use the Apigee Console for Salesforce.com as your client application and start writing some code!
If you want some real-world experience using Apex REST services, hop on over to CloudSpokes and participate in some of our challenges to rebuild our site using Apex REST services. Prizes range from $500 – $1000 and we have a large number of challenges available!

Written by Jeff Douglas

July 21, 2011 at 8:55 am

Posted in CloudSpokes, REST

Tagged with

Changing tab logo using styling

leave a comment »

Here is a step by step guide on how to add custom logo to a VisualforceTab

The following post is an answer to a question that was posted on the discussion board.

Step 1:

Create a Visualforce Tab

Username >Setup >App Setup >Create >Tabs> VisualforceTabs >New

 

 

 

 

 

 

 

 

 

 

 

Select Tabstyle

Step 2:

Click on Create your own style

 

 

 

 

 

 

 

 

 

 

 

 

Step 3:

Select color and insert your custom image.

 

 

 

 

 

 

 

 

 

 

 

Final step:

Navigate to the tab and it should display the custom logo

Please note: you need the following tag within your visualforce page to display the section header

<apex:sectionHeader title=”Teach Me Salesforce” />

Written by Chris O Davies

July 16, 2011 at 8:48 am

Posted in Intermediate, Visualforce

Tagged with

Follow

Get every new post delivered to your Inbox.

Join 163 other followers