Saturday, 26 November 2011

Retrieve Related Object Fields

A short post this week, again based on a question from the DeveloperForce Visualforce Discussion Board regarding the retrieval of a related objects fields.  In this particular case, an sobject is being created/edited via Visualforce and once a lookup field is populated, the developer wanted to display some fields related to the lookup.

Unsurprisingly, simply adding some outputfields for the  related object as shown below doesn't do the trick:


<apex:page standardcontroller="Contact">
 <apex:form >
   <apex:pageBlock title="Contact Create/Edit">
      <apex:pageBlockSection title="Contact Information">
       <apex:inputField value="{!contact.FirstName}"/>
       <apex:inputField value="{!contact.LastName}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Account Information">
       <apex:inputField value="{!contact.AccountId}"/>
       <apex:outputField value="{!contact.Account.AccountNumber}"/>
       <apex:outputField value="{!contact.Account.Site}"/>
      </apex:pageBlockSection>
   </apex:pageBlock>
  </apex:form>
</apex:page>

Once the lookup is selected, the outputfield values remain empty, as all that has been specified is the id of the account via the inputfield - the related object details aren't populated in the sobject graph, so attempting to traverse the account relationship doesn't bring back any data:



One way to get the information back would be to save the contact once the lookup is populated and carry out a client side redirect to the current page with the id of the newly created contact.  When the standard controller retrieves the new sobject, it will populate the related Account information automatically.  This isn't a great user experience though, as the save is likely to be well before the user is ready, and doesn't give them a chance to change their mind part way through.  Likely to lead to a lot of unwanted and half-populated contacts.

Therefore it looks like an extension controller is the route to go. The page has been updated with an actionsupport component that is attached to the account lookup. This invokes an action method on the extension controller which executes a SOQL query to populate the related account information.

Revised Page:

<apex:page standardcontroller="Contact" extensions="RelatedController">
 <apex:form >
   <apex:pageMessages id="msgs"/>
   <apex:pageBlock title="Contact Create/Edit">
      <apex:pageBlockSection title="Contact Information">
       <apex:inputField value="{!contact.FirstName}"/>
       <apex:inputField value="{!contact.LastName}"/>
      </apex:pageBlockSection>
       <apex:actionRegion >
          <apex:pageBlockSection id="accinfo" title="Account Information">
         <apex:inputField value="{!contact.AccountId}">
            <apex:actionSupport event="onchange" action="{!AccountPopulated}" rerender="accinfo, msgs"/> 
         </apex:inputField>
         <apex:outputField value="{!contact.Account.AccountNumber}"/>
         <apex:outputField value="{!contact.Account.Site}"/>
        </apex:pageBlockSection>
      </apex:actionRegion>
      <apex:pageBlockButtons >
        <apex:commandButton value="Cancel" action="{!cancel}"/>
        <apex:commandButton value="Save" action="{!save}"/>
      </apex:pageBlockButtons>
   </apex:pageBlock>
  </apex:form>
</apex:page>

Extension controller:

public with sharing class RelatedController 
{
 private ApexPages.StandardController stdCtrl;
 
 public RelatedController(ApexPages.StandardController std)
 {
  stdCtrl=std;
 }
 
 public void AccountPopulated()
 {
  Contact cont=(Contact) stdCtrl.getRecord();
  cont.Account=[select AccountNumber, Site from Account where id=:cont.AccountId];
 }
}
Choosing the account now carries out an Ajax request and rerenders the account information with the fields populated.


Also, as I've been able to write the information into the object relationship, its the same markup that renders the account information regardless of whether it was retrieved via my extension controller or via the standard controller "reflection" when the page is opened with a specified contact id.

Finally, a word of advice - if you are writing an extension controller for this purpose, make sure that the related object has some data present in the fields, otherwise you'll be convinced your code is failing when in fact its working perfectly, just rendering empty fields!

Saturday, 19 November 2011

Bypass Validation Rules from Apex

Today's post concerns a topic that crops up pretty frequently on the Developerforce Discussion Boards - how to allow apex code to bypass validation rules.

In  my example scenario, I've created a validation rule against the Contact standard object that checks either the contact's email address or phone number has been provided:

Thus when I attempt to create a Contact via the UI, if I don't provide one of these pieces of information I receive an error message:


All well and good.  However, what about the situation where I am creating a Contact programmatically? In this case the validation rule is still applied:


Obviously creating via the system log is a contrived example, but in practice I may be receiving the details from an external system as part of an integration (via an apex web service) or data migration (via the Apex Data Loader), and the information may simply not be available in that system.  If its a data migration, I could  disable the validation rule for the duration, but if its a real-time integration that isn't an option.

The solution I came up with was to provide a mechanism for the record itself to indicate how it was being created/edited and to take that into account for in the validation rule.

The first step in the solution is to create a new custom field on the Contact object - a checkbox that if set to true indicates the record is being processed in an apex context.  This checkbox is not made available on any page layouts, as we don't want regular UI users to be able to check it.


Next, change the validation rule to allow both the email and phone fields to be empty if the apex context field is set to true:


Its tempting at this time to think that the problem is now solved - the Apex Context field can simply be set to true and the validation rule will allow the record through.  However, the record will then be saved to the database with the Apex Content field set to true, and the next time that a user edits the record in the UI, the validation rule will be bypassed - not the behaviour I am looking for.  Therefore I've created a workflow rule and associated field update to unset the checkbox whenever it is found to be set.

Workflow rule:


Field update:


and that's all there is to it.  Now I can create a contact via the system log without providing the email or phone field:


But if I then navigate to that contact via the UI and attempt to edit the record without providing these fields, the validation rule blocks this:



Saturday, 12 November 2011

Automatically Add User to Chatter Group

Following on from an earlier blog post that dealt with user autofollowing, I was recently asked about automatically adding users to a chatter group.  Here at BrightGen we have a chatter group that everyone gets added to when a user is created for them on our Salesforce instance.  Its not too arduous to add these users to the group, but every now and then one gets overlooked.

Chatter groups are stored as CollaborationGroup records, with the members of the group stored in CollaborationGroupMember records related via the CollaborationGroupId field.

The first step is to create a trigger that is executed wheh a user record is added.  I've reused the trigger I created for my autofollow user post:


trigger ai_user on User (after insert) 
{
 ChatterAutofollow.AutofollowUsers(trigger.newMap.keySet());
 ChatterAutoFollow.AddToGroups(trigger.newMap.keySet());
}

The second line, containing the call to the AddToGroups is the new part - this is the method that does the heavy lifting.

@future
public static void AddToGroups(Set<Id> userIds)
{
 List<User> users=[select id, Username from User where id in :userIds];
 
 // set up the groups that the user should be added to
 List<String> groups=new List<String>{'All Users'};
 
    List<CollaborationGroup> chatterGroups=[select id, Name from CollaborationGroup where name in :groups];

 List<CollaborationGroupMember> chatterGroupMembers=new List<CollaborationGroupMember>();
 List<FeedPost> feedPosts=new List<FeedPost>();
      
     // loop the users that have been created
     for (User user : users)
     {
                // loop the groups
      for (CollaborationGroup chatterGroup : chatterGroups)
      {
                        // add the user to the group
   CollaborationGroupMember cand = 
       new CollaborationGroupMember(
                      CollaborationGroupId=chatterGroup.id,
                            MemberId = user.Id);
       chatterGroupMembers.add(cand);
                        // announce this!
          FeedPost fpost = new FeedPost(ParentId=chatterGroup.id,
                Body = user.username + ' added to group via Bob Buzzard trigger');
                    feedPosts.add(fpost);
          fpost = new FeedPost(ParentId=user.id,
                    Body = 'You have been added to the ' + chatterGroup.name + ' group via Bob Buzzard trigger');
                  feedPosts.add(fpost);
  }
 }
    
 insert chatterGroupMembers;
 insert feedPosts;
}

as before, the method has to be declared with the @future annotation, as the trigger is invoked when a User is inserted and I'll otherwise hit mixed mode DML problems.

The group name is hardcoded for the purposes of this blog - not a good idea in practice and for a production system I'd store this information in a custom setting.  Once the groups have been retrieved, the inserted users are iterated and added to each of the groups.  As an added flourish I've added a post to the group to notify members the new user has joined, and a post to the new user's feed to let them know this has happened.  As I'm keen to grab all the credit that is going, I've also mentioned in each post that this was done via my trigger!

Now I can add a user via the UI:


Once the user is created, they are automatically added to the All Users group and a post announces this:


And navigating to the new users profile, shows a post on their chatter feed informing them this has taken place:


And finally, a shout out to my BrightGen colleage Zak Crammond who asked the question that led to this post.

Saturday, 29 October 2011

Creating Attachments in Apex Code

Capturing notes and attachments against records is standard Salesforce functionality.  Recently I had a requirement to capture some notes and a subset of the record details at various points of the record's life.  Effectively the notes were specific to the record's current state and viewing either in isolation wouldn't give the desired outcome. While it would be possible to handle this with a combination of notes/attachments and field history tracking, that puts far too much emphasis on the user to pull the information from disparate places.

To satisfy this requirement I came up with a Visualforce page that is embedded in a record view and has a notes field to capture the free text plus a checkbox to indicate if the opportunity detail should be captured.  The page embedded into an Opportunity record is shown below:



The fields that are included into the attachment are coded into the page and controller in this case, but it would be straightforward to use Visualforce field sets to reduce the maintenance overhead.  The Visualforce markup is shown below:


<apex:page standardController="Opportunity" extensions="OpportunityAttachmentController">
  <apex:outputText value="{!Opportunity.Account.Name}" rendered="false"/>
  <apex:outputText value="{!Opportunity.CloseDate}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Amount}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.StageName}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Probability}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.ExpectedRevenue}" rendered="false"/>
   
  <c:RefreshEmbeddedComponent id="refresher" location="/{!Opportunity.id}" active="{!refreshPage}"/>
  <apex:form >
   <apex:pageBlock title="Add Attachment" mode="mainDetail" id="pb">
      <apex:pageBlockSection columns="2">
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="title" value="Title"/>
            <apex:inputText id="title" value="{!title}"/>
         </apex:pageBlockSectionItem>
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="incsum" value="Include Opportunity Detail?"/>
            <apex:inputCheckbox id="incsum" value="{!includeOppDetail}"/>
         </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:pageBlockSection columns="1">
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="notes" value="Notes"/>
            <apex:inputTextArea rows="15" style="width:80%" id="notes" value="{!notes}"/>
         </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:commandButton value="Add As Attachment" action="{!addAttachment}" rerender="refresher"/>
   </apex:pageBlock>
  </apex:form>
</apex:page>

The first part of the page simply pulls in the fields that are required by the controller. Note that these aren't rendered or used in the page, but without these components my controller would have to query the details from the database:
<apex:outputText value="{!Opportunity.Account.Name}" rendered="false"/>
  <apex:outputText value="{!Opportunity.CloseDate}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Amount}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.StageName}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Probability}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.ExpectedRevenue}" rendered="false"/>

The only other aspect that isn't vanilla Visualforce forms is the following custom component:

<c:RefreshEmbeddedComponent id="refresher" location="/{!Opportunity.id}" active="{!refreshPage}"/>

This allows the entire record view page to be refreshed once a Boolean value from the controller is set to true - its the functionality from an earlier post embedded into a component to promote reuse.

Once the user has filled in the form, the following Apex action method is invoked:

public void addAttachment()
{
 String bodyStr=title + '\n' + 
                notes;
  
 if (includeOppDetail)
 {
  bodyStr+='\n\nOpportunity Summary\n' +
           'Account      : ' + opp.Account.Name + '\n' + 
           'Close Date   : ' + opp.CloseDate + '\n' + 
           'Amount       : ' + opp.Amount + '\n' + 
           'Stage        : ' + opp.StageName + '\n' + 
           'Probability  : ' + opp.Probability + '\n' + 
           'Expected Rev : ' + opp.ExpectedRevenue;
 }
 bodyStr+='\n\n\n' + 
           System.now();
  
 Attachment att=new Attachment();
 att.Body=Blob.valueOf(bodyStr);
 att.Name='Note_' + System.now().format('yyyy_MM_dd_hh_mm_ss') + '.txt';
 att.parentId=opp.id;
 insert att;
  
 refreshPage=true;
}

The first part of the method simply builds up the text body of the attachment in a String, appending the user's notes and, if requested, a snapshot of the opportunity fields.

The interesting part is where the attachment is created:
Attachment att=new Attachment();
att.Body=Blob.valueOf(bodyStr);
att.Name='Note_' + System.now().format('yyyy_MM_dd_hh_mm_ss') + '.txt';
att.parentId=opp.id;
insert att;


As you can see there's not much to do - simply create a new attachment, convert the String containing the body to a Blob and store that in the attachment's body field, set up the name and which record its attached to and then insert. The final line sets the flag to refresh the record view page.

Sunday, 23 October 2011

London Force.com Developer Meetup #3

This week saw the third London Force.com Developer meetup take place, at the Skills Matter eXchange in Clerkenwell.  A slightly different format this time, with five lightning talks.  Click on the links at the start of each section to view the podcasts.

First up was Amjad Khan on Schema Spy.  Schema Spy is a Java tool that analyses databases and produces a graphical representation of the schema.  A plugin links this to your Salesforce instance.  This is a tool that I've used once or twice in the past, but not that frequently as one of the BrightGen deliverables from the scoping stage of a project is a diagram of the data model.  Where it has come in very handy is when we've been engaged to extend an existing solution or provide a managed service - it saves a lot of analysis time in these cases.

The new Schema Builder functionality looks likely to replace this for us in the fullness of time, mainly as it is in the cloud, and anything that gets software off of my laptop gets my vote.

Next up was Simon Goodyear on interfaces.  For some reason I'd convinced myself that this was going to be around user interfaces, rather than the programming construct as it turned out to be! Interfaces are something that I tend to use when developing products rather than carrying out a Salesforce implementation, on the assumption that I'm likely to want to swap out implementations as the platform moves on, but customers are unlikely to want (or to want to pay for!, even though the cost is usually pretty low) this level of flexibility. Usually Salesforce implementations are seen as a single-shot roll out with some additional maintenance work as time goes on.

A real-world example of interfaces for us was a custom calendar implementation.  The standard Salesforce activity functionality didn't give the flexibility that was required, Initially there were three sobject types that could feature in calendars, which were people, rooms and equipment.  It seemed highly likely that there would be other sobject types required to work in this way, and indeed this turned out to be the case.  Thus the calendar functionality was implemented to work with a Calendarable interface, and wrapper classes to encapsulate the sobjects along with an implementation of the Calendarable interface were written.   Supporting a new sobject type in the calendar was reduced to a small custom class with 10s of lines of code, rather than tweaking code all over the calendar implementation.

Next up was yours truly on the subject of testing.  I'm planning to use this as the basis for a blog post dedicated to testing, so won't say anything more here.

Stony Grunow then spoke on effective B2C management.  I found this a particularly useful talk, as I'm often having to field questions about why person accounts aren't suitable for an implementation, usually because additional, secondary contacts are required to be associated, for example spouse and children.  I'll definitely be taking a look at the Contacts and Organizations package.

Finally, Bruce Durling spoke on using Selenium to automate deployments.  Deploying configuration has always been a challenge, as there are some things that can be deployed via metadata - approval processes for example.  Re-keying these is error-prone, even when using the four eyes principle.  Using Selenium for this is an intriguing notion.  A key factor in deciding whether to go this route would be the amount of times that the deployment will take place.  If its a single shot, then I can't see there would be much benefit, as you would be effectively re-keying it through code that drives Selenium.    Where it becomes much more useful is where changes have to be deployed through various developer sandboxes, QA and pre-production environments before ending up on production.

I can also see this being a useful technique for creating custom setting values, which aren't copied over for anything other than full copy sandboxes, but supply vital configuration information to allow the system to function correctly.  It may even provide a solution to the perennial problem of determining the HTML element ids of fields to populate via URL parameters.  We use custom settings to capture this information, but there's still manual intervention required to scrape the id out of the page. Something I'll be investigating in the near future.

With all this, excellent before and after networking, and free pizza and beer, its easy to see why these meetups are growing in popularity.  Once again, thanks go to Wes Nolte (@wesnolte) and Bruce Durling (@otfrom) for organizing everything.  If you are interested in coming along to the next one, follow any (or all!) of these twitter users and the details will be (re)tweeted as soon as they are confirmed: @bob_buzzard, @otfrom, @wesnolte.  Look forward to seeing you there!

Saturday, 15 October 2011

Dojo Charts Part 4 - Line Charts

This week the Dojo chart under the microscope is the line chart, where a series of points are plotted on the graph and joined by lines.  In my example there are two series allowing comparison of made up information about the English counties of Suffolk and Norfolk.

Following the now familiar pattern is a page with associated controller that pulls in the cross domain Dojo build, manages the data and delegates the chart production to a custom component:


<apex:page controller="DojoLineChartController">
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true,
      modulePaths: { 
          'dojo': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo', 
          'dijit': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit', 
         'dojox': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox' 
             }
    ">
</script>

<apex:form >
  <table>
    <tr>
      <td>Labels:</td>
      <td>
         <apex:inputText value="{!labels}" size="80"/>
      </td>
    </tr>
    <tr>
      <td>Series 1:</td>
      <td>
        <apex:inputText value="{!series1}"/>
      </td>
    </tr>
    <tr>
      <td>Series 2:</td>
      <td>
        <apex:inputText value="{!series2}"/>
      </td>
    </tr>
  </table>
  <apex:commandButton value="Update"/>
</apex:form>
<c:DojoLineChart id="linechart" miny="0" maxy="12" stepy="3" 
        labelsx="{!xAxisLabels}"   
        title1="Essex" 
        series1="{!series1}"
                 color1="#000"
        title2="Norfolk" 
        series2="{!series2}"
                 color2="#C00"
        />
</apex:page>

The controller manages three String properties:

  • A comma separated list of labels, used to produce the x axis labels
  • A comma separated list of values for the first series to plot
  • A comma separated list of values for the second series to plot

<apex:component >
  <apex:attribute name="miny" description="Minimum Y Axis value" type="Integer"/>
  <apex:attribute name="maxy" description="Maximum Y Axis value" type="Integer"/>
  <apex:attribute name="stepy" description="Maximum Y Axis value" type="Integer"/>
  <apex:attribute name="labelsx" description="X Axis Labels" type="String" />
  <apex:attribute name="title1" description="Plot series 1 title - displayed in legend" type="String" />
  <apex:attribute name="series1" description="Plot series 1, comma separated values" type="String" />
  <apex:attribute name="color1" description="Plot series 1 colour" type="String" />
  <apex:attribute name="title2" description="Plot series 2 title - displayed in legend" type="String" />
  <apex:attribute name="series2" description="Plot series 2, comma separated values" type="String" />
  <apex:attribute name="color2" description="Plot series 2 colour" type="String" />
  <apex:attribute name="title3" description="Plot series 3 title - displayed in legend" type="String" />
  <apex:attribute name="series3" description="Plot series 3, comma separated values" type="String" />
  <apex:attribute name="color3" description="Plot series 3colour" type="String" />
  <apex:attribute name="height" description="Chart Height" type="Integer" default="300"/>
  <apex:attribute name="width" description="Chart Width" type="Integer" default="800"/>
  <apex:attribute name="showLegend" description="Show Legend" type="Boolean" default="true"/>
  
  
<script type="text/javascript">

  dojo.require("dojox.charting.Chart2D");
  dojo.require("dojox.charting.widget.Legend");

  makeCharts = function(){

        var chart1 = new dojox.charting.Chart2D("simplechart", {fill:"transparent"});
        chart1.addPlot("default", {type: "Lines", markers: "true", hAxis: "x", vAxis: "y"});
        chart1.addAxis("x", {
       <apex:outputText rendered="{!NOT(ISBLANK(labelsx))}">
            labels:
               [
                 {!labelsx}
               ]
            </apex:outputText>
  });
  
        chart1.addAxis("y", 
                      {
                       <apex:outputText value="min:{!miny}," rendered="{!NOT(ISBLANK(miny))}"/> 
                       <apex:outputText value="max:{!maxy}," rendered="{!NOT(ISBLANK(maxy))}"/> 
                       <apex:outputText value="majorTickStep:{!stepy}," rendered="{!NOT(ISBLANK(stepy))}"/> 
                       vertical: true, 
                       natural: false});
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title1))}">
           chart1.addSeries("{!title1}", [{!series1}],
                {plot: "other", stroke: {color:"{!color1}"}});
        </apex:outputPanel>
        
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title2))}">
           chart1.addSeries("{!title2}", [{!series2}],
                {plot: "other", stroke: {color:"{!color2}"}});
        </apex:outputPanel>
        
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title3))}">
           chart1.addSeries("{!title3}", [{!series3}],
                {plot: "other", stroke: {color:"{!color3}"}});
        </apex:outputPanel>
        chart1.render();
        <apex:outputText rendered="{!showLegend}">
  var legend1 = new dojox.charting.widget.Legend({chart: chart1, horizontal: false}, "legend1");
  </apex:outputText>        
};

dojo.addOnLoad(makeCharts);

</script>

<table>
   <tr>
     <td>
    <div id="simplechart" style="width: 800px; height: 300px;"></div>
  </td>
     <td style="vertical-align:middle">
    <div id="legend1"></div>
  </td>
   </tr>
</table> 
 
</apex:component>

The chart is created with a plot and x and y axes. The component can take up to
3 series - these are conditionally added to to chart via apex:outputPanel
components, along with the line colour. The component can also display a legend
for the chart - if the showLegend attribute is set to true. When first opened,
the page appears as:




Updating the values in the chart and clicking the Update button causes the graph to be redrawn with the  new values:


This example, like the others in this series, is available in the Google Code project

Sunday, 9 October 2011

Dojo Charts Part 3 - Stacked Bar Charts

Continuing the series of posts on Dojo charting, this week I'm looking at stacked bar charts.  This is nboStacked bar charts allow a single bar to display more than one category of data.  My example shows the SLA status for a set of records.

As usual, the Visualforce page pulls in the cross domain Dojo build from Google, and delegates responsibility for the production of the chart to a custom component:


<apex:page >
	 <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"
    	djConfig="parseOnLoad: true,
	  	modulePaths: { 
    		'dojo': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo', 
    		'dijit': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit', 
    		'dojox': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox' 
  				}
    	">
    </script>
    
  <apex:sectionheader title="Dojo Stacked Bar Chart"/>    	
	<c:DojoStackedBarChart name1="Inside SLA"
		series1="1,3,1" 
		colour1="green"
		click1="http://www.google.com"
		name2="Escalated"
		series2="3,8,5" 
		colour2="yellow"
		click2="http://www.google.com"
		name3="Breached SLA"
		series3="4,13,8" 
		colour3="red"
		click3="http://www.google.com" 
		divId="stackedBars"
		title="SLA Status"
		/>
</apex:page>
each stacked bar in the chart is set up via the series/colour/click/name indexed parameters. I haven't made this a live page, but it would be straightforward to store the series1-3 values in a custom controller and add apex:inputField components that allowed the user to update them. Where this differs from previous posts is the click parameters - this allows the user to click on one of the stacked bars and be taken to another page. I've simply sent the user to www.google.com, but when I've used this technique in a real project the user is taken to a drill down page or report that shows the detail behind that particular stacked bar. The component markup is shown below:
<apex:component >
<apex:attribute name="name1" type="String" description="Name of first series (first stack)"/>
<apex:attribute name="series1" type="String" description="Comma seperated values for first series"/>
<apex:attribute name="colour1" type="String" description="Colour of first series"/>
<apex:attribute name="click1" type="String" description="The destination if the user clicks on the first series"/>
<apex:attribute name="name2" type="String" description="Name of second series (second stack)"/>
<apex:attribute name="series2" type="String" description="Comma seperated values for second series"/>
<apex:attribute name="colour2" type="String" description="Colour of second series"/>
<apex:attribute name="click2" type="String" description="The destination if the user clicks on the first series"/>
<apex:attribute name="name3" type="String" description="Name of third series (third stack)"/>
<apex:attribute name="series3" type="String" description="Comma seperated values for first series"/>
<apex:attribute name="colour3" type="String" description="Colour of first series"/>
<apex:attribute name="click3" type="String" description="The destination if the user clicks on the first series"/>
<apex:attribute name="divId" type="String" description="ID of the div to store the chart in"/>
<apex:attribute name="title" type="String" description="Title of the chart"/>

<script type="text/javascript">
dojo.require("dojox.charting.Chart2D");
dojo.require("dojo.colors");
dojo.require("dojox.charting.themes.Tufte");
dojo.require("dojox.charting.widget.Legend");
	
makeCharts = function(){
		var chart2=new dojox.charting.Chart2D("{!divId}");
		// use the Tufte theme as this gives transparent background
        chart2.setTheme(dojox.charting.themes.Tufte);
        chart2.addPlot("default", {type: "StackedBars"});
        
        chart2.addSeries("{!name1}", [{!series1}], {fill: "{!colour1}"});
        chart2.addSeries("{!name2}", [{!series2}], {fill: "{!colour2}"});
        chart2.addSeries("{!name3}", [{!series3}], {fill: "{!colour3}"});
        chart2.addAxis("x", {includeZero: true});
        
        chart2.connectToPlot("default", this, function(evt)
           {
				if(!evt.shape||(evt.type != 'onclick')){
					return;
				}
				var link="";
				if (evt.run.name=="{!name1}")
				{
				   link="{!click1}";
				}
				else if (evt.run.name=="{!name2}")
				{
				   link="{!click2}";
				}
				else if (evt.run.name=="{!name3}")
				{
				   link="{!click3}";
				}
                if (""!=link)
                {
    				window.open(link, "_blank");
                }
           }
        );
        chart2.render(); 
		var legend1 = new dojox.charting.widget.Legend({chart: chart2}, "legend1");
};

dojo.addOnLoad(makeCharts);

</script>

<div id="{!divId}" style="width: 600px; height: 200px;"></div>
<div id="legend1"></div>
<div style="margin-top: 40px; font-size:14px; font-weight:bold; width: 400px; text-align:center">{!title}</div>
</apex:component>
Most of this will be familiar to regular readers - create the chart, define the series, add plots and axes etc. The interesting part (for this week at least) is the function that handles the event when the user clicks on a stacked bar.
   chart2.connectToPlot("default", this, function(evt)
           {
				if(!evt.shape||(evt.type != 'onclick')){
					return;
				}
				var link="";
				if (evt.run.name=="{!name1}")
				{
				   link="{!click1}";
				}
				else if (evt.run.name=="{!name2}")
				{
				   link="{!click2}";
				}
				else if (evt.run.name=="{!name3}")
				{
				   link="{!click3}";
				}
                if (""!=link)
                {
    				window.open(link, "_blank");
                }
           }
        );
The bar can be identified based on the run.name attribute of the event and is simply matched up with the associated click parameter. The generated chart is shown below: