Teach Me Salesforce

A community approach to learning salesforce.com

Author Archive

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

Advertisements

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

JavaScript Remoting

with 2 comments

One of the major enhancements from visualforce this release(Summer 11) and my favorite is JavaScript remoting.

JavaScript Remoting is the ability to invoke apex class method from javascript embedded in a visualforce page.JavaScript Remoting is now available after summer 11 release.

Here is a quick example : we have a Apex class which defines a get Account method.

global with sharing class MyJSRemoting
{
    public static Account account { get; set; }

    @RemoteAction
    global static Account getAccount(String accountName) {
        account = [select id, name, phone, type, numberofemployees from
             Account where name = :accountName limit 1];
        return account;
    }
}

This method is having @RemoteAction annotation making it available to be called from a visualforce page. Now in our visualforce page we have a script tag set which is how we embed javascript within visualforce pages.

<apex:page controller="MyJSRemoting">

<script type="text/javascript">
function getAccountJS() {
    var accountNameJS = document.getElementById('accountname').value;

    ankit.MyJSRemoting.getAccount( accountNameJS, function(result, event)
    {
        if (event.status)
        {
           document.getElementById("{!$Component.theBlock.pbs.pbsi2.accId}")
                                  .innerHTML = result.Id;
           document.getElementById("{!$Component.theBlock.pbs.pbsi1.name}")
                                  .innerHTML = result.Name;
        }
    }, {escape:true});
}
</script>

    <input id="accountname" type="text">

    <button onclick="getAccountJS();">Get Account</button>

    <apex:pageblock id="theBlock">
        <apex:pageblocksection columns="2" id="pbs">

            <apex:pageblocksectionitem id="pbsi1">
                 <apex:outputtext id="name">
            </apex:outputtext></apex:pageblocksectionitem>

            <apex:pageblocksectionitem id="pbsi2">
                <apex:outputtext id="accId">
            </apex:outputtext></apex:pageblocksectionitem>

        </apex:pageblocksection>
    </apex:pageblock>
</apex:page>

This JavaScript code then invokes the getAccount method in the apex class to take action.

Please note I have used namespace “ankit.MyJSRemoting” as there is a namespace registered on my organization.

JavaScript Remoting is the feature which is primarily be used by developers and will allow them to create richer and more interactive user interfaces that everyone will benefit from.

In particular, lets developers create rich javascript based user interfaces by invoking apex controller methods within JavaScript code. JavaScript is typically used to render small parts of user interface. Result from a Javascript remoting call are much faster then using a typical visualforce reRender model, developers can provide more instant feedback to the user. This is because remoting calls are stateless. Means only data is set back and forth instead a lot of bulky HTML.

Additional Visualforce enhancements :

1) Filtered Lookup via Visualforce.
2) Inline editing for rich text area field.
3) Field Set property accessors.

This is a cross post to my Blog.

Written by Ankit Arora (forceguru)

June 19, 2011 at 4:33 pm

Visualforce Code Generator

with 9 comments

This is a cross post from my Blog Post.

We know how much important role visualforce plays in our application. There are many instance where we need to create a visualforce page and apex class. Salesforce provides powerful tools like workflows, approvals, validation etc. from which we can implement our business functionalities with just button clicks.

Now I was having a requirement where I need to create many visualforce pages, some are custom and some are cloning native page with native layouts with some small changes. This just clicked me to create a tool from which we can create a visualforce page code with just clicking buttons. Yes!! Visualforce page code with button click.

This saved a lot of time, as I do not need to write any thing to create a heavy visualforce page and amazingly it is done just using button clicks.

Install the package : https://login.salesforce.com/packaging/installPackage.apexp?p0=04t90000000Pqos

Just append “/apex/vfgenerator__codegenerator” in URL.

Now let me jump into the explanation what exactly I have done. A simple UI will be displayed, where user can select the desired type of page, object name, record type, email field.

Object Name : Valid object API name (include namespace if any)

Record Type : Record type Id of object selected in Object Name
Type of Page :

  1. Edit : Code will be generated for the edit page of the object (according to “record type” layout if selected any).
  2. Detail : Code will be generated for the detail page of the object (according to “record type” layout if selected any)
  3. Custom Detail : Code will be generated for detail page according to the fields selected from UI
  4. Custom Edit : Code will be generated for edit page according to the fields selected from UI

In the above screen I have selected “Edit” as the type of page and “Account” in object, also provided my email. When I click “Generate Code” it displays like this :

Just copy the code and paste it in new visualforce page. Why I have given my email id because when you paste the code in visualforce page it will loose all the formatting and will display in one single line, but code sent on email will come with formatting. So we can copy the generated code from email also. Now when you hit the newly created visualforce page it will display all fields in edit mode which are displayed on native edit page. Isn’t that good?

Now lets try creating some custom visualforce page. Select “Custom Edit” in “Type of Page” and “Account” in “Object Name”. It will display a section “Select Fields”. Click on “Display Fields”, it will display all fields which are up datable by present user.

Select some fields and click on  “Generate Code”. Again copy paste the code in your visualforce page, it will display selected fields in edit mode.

Now heavy visualforce pages are created with just button clicks.

Package provided is managed because the tool is under testing and I would like to have feedback from you all. Once it is free from all bugs I will provide the code.

Declaration: You may find similar post related to “Visualforce Code Generator”. The end result may be similar but there is a considerable difference in the approaches being followed here. So I, hereby declare that my project is entirely my own creation and has not been copied from any other person/organization’s words or idea. Please feel free to drop me an email at “arora.salesforce@gmail.com” if there is any disagreement.

Cheers

Written by Ankit Arora (forceguru)

June 15, 2011 at 2:32 pm

How Salesforce 18 Digit Id Is Calculated

with 6 comments

As we know that each record Id represents a unique record within an organization. There are two versions of every record Id in salesforce :

  • 15 digit case-sensitive version which is referenced in the UI
  • 18 digit case-insensitive version which is referenced through the API
The last 3 digits of the 18 digit Id is a checksum of the capitalization of the first 15 characters, this Id length was created as a workaround to legacy system which were not compatible with case-sensitive Ids. The API will accept the 15 digit Id as input but will always return the 18 digit Id.
Now how we can calculate the 18 digit Id from 15 digit Id ??? Just copy paste the below code in system logs and pass your 15 digit id in String id
//Your 15 Digit Id
String id= '00570000001ZwTi' ;

string suffix = '';
integer flags;

for (integer i = 0; i < 3; i++)
{
	flags = 0;
	for (integer j = 0; j < 5; j++)
	{
		string c = id.substring(i * 5 + j,i * 5 + j + 1);
		//Only add to flags if c is an uppercase letter:
		if (c.toUpperCase().equals(c) && c >= 'A' && c <= 'Z')
		{
			flags = flags + (1 << j);
		}
	}
	if (flags <= 25)
	{
		suffix = suffix + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.substring(flags,flags+1);
	}
	else
	{
		suffix = suffix + '012345'.substring(flags-25,flags-24);
	}
}

//18 Digit Id with checksum
System.debug(' ::::::: ' + id + suffix) ;

This debug will return the 18 digit Id.

This is a cross post to my Blog

Written by Ankit Arora (forceguru)

June 6, 2011 at 3:47 pm

Posted in Apex, Beginner, Code Sample

Sending more than 10 E-mails from Apex

with 5 comments

Most of time we stuck with limits which salesforce.com pull over us. I found this interesting as this push me to explore more how we can overcome this.

Now we have a limit of using “sendEmail” in apex and it is 10 times. Isn’t this bad??

This is a cross-post to my Blog.

Now when I am dealing with anything in bulk first thing comes to my mind is Batch Class. Now what is the scenario which leads me to send more than 10 emails?? I have 15 system administrators in my organization and each logs 2-3 cases in a day. Now I want to send an email which contains details of case logged by each system administrator separately, means To address and Body of each email will be different.

So just create a batch class with this code :

global class Batch_CaseEmail implements Database.Batchable<sObject>,Database.Stateful
{
   Map<Id , List<Case>> userCaseMap {get; set;}
   List<Case> allCaseLoggedToday {get; set;}

   global Batch_CaseEmail()
   {
       //Map to maintain user id and cases logged by them today
       userCaseMap = new Map<Id, List<Case>>() ;

       //All sales rep (System admins)
       List<User> salesRep = new List<User>() ;
       salesRep = [select id , name , Email , ManagerId from User
                    where Profile.Name = 'System Administrator'] ;

       //All sales rep ids
       List<Id> salesIds = new List<Id>() ;
       for(User ur : salesRep)
       {
           salesIds.add(ur.Id) ;
       }

       //All cases logged today by sales rep
       allCaseLoggedToday = new List<Case>() ;
       allCaseLoggedToday = [select Id, CaseNumber,CreatedById, Owner.name 
                            , account.Name , contact.name from Case 
                            where CreatedDate = TODAY AND CreatedById in : salesIds] ;
   }

   global Database.QueryLocator start(Database.BatchableContext BC)
   {
      //Creating map of user id with cases logged today by them
      for(Case c : allCaseLoggedToday)
      {
          if(userCaseMap.containsKey(c.CreatedById))
          {
              //Fetch the list of case and add the new case in it
              List<Case> tempList = userCaseMap.get(c.CreatedById) ;
              tempList.add(c);
              //Putting the refreshed case list in map
              userCaseMap.put(c.CreatedById , tempList) ;
          }
          else
          {
              //Creating a list of case and outting it in map
              userCaseMap.put(c.CreatedById , new List<Case>{c}) ;
          }
      }

      //Batch on all system admins (sales rep)
      String query = 'select id , name , Email from User 
                     where Profile.Name = \'System Administrator\'';
      return Database.getQueryLocator(query);
   }

   global void execute(Database.BatchableContext BC, List<sObject> scope)
   {

      for(Sobject s : scope)
      {
          //Type cast sObject in user object
          User ur = (User)s ;

          //If system admin has logged any case today then only mail will be sent
          if(userCaseMap.containsKey(ur.Id))
          {
              //Fetching all cases logged by sys admin
              List<Case> allCasesOfSalesRep = userCaseMap.get(ur.Id) ;

              String body = '' ;
              //Creating tabular format for the case details
              body = BodyFormat(allCasesOfSalesRep) ;

              //Sending Mail
              Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage() ;

              //Setting user email in to address
              String[] toAddresses = new String[] {ur.Email} ;

              // Assign the addresses for the To and CC lists to the mail object
              mail.setToAddresses(toAddresses) ;

              //Email subject to be changed
              mail.setSubject('New Case Logged');

              //Body of email
              mail.setHtmlBody('Hi ' + ur.Name + ',

 Details of Cases logged today is as follows : 

' + body + '
 Thanks');

              //Sending the email
              Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
          }
      }
   }

   public String BodyFormat(List<Case> lst)
   {
       String str = '' ;
       for(Case cs : lst)
       {
           str += '<tr><td>'+ cs.CaseNumber +'</td>'+'<td>'+ cs.Owner.Name 
                   +'</td>'+'<td>'+ cs.Account.Name +'</td>'
                   +'<td>'+ cs.Contact.Name +'</td>'+'</tr>' ;
       }
       str = str.replace('null' , '') ;
       String finalStr = '' ;
       finalStr = '<table border="1"> <td> CaseNumber </td> <td> Owner </td> 
                   <td> Account </td> <td> Contact </td> ' + str +'</table>' ;
       return finalStr ;
   }

   global void finish(Database.BatchableContext BC)
   {
   }

}

Code is self explanatory, hope there will be no issues understanding it.

Execute this class using below script in system logs :

Batch_CaseEmail controller = new Batch_CaseEmail() ;
Integer batchSize = 1;
database.executebatch(controller , batchSize);

All system administrators will get an email something like this :

Email Format

Written by Ankit Arora (forceguru)

June 5, 2011 at 2:12 pm