Showing posts sorted by relevance for query dojo. Sort by date Show all posts
Showing posts sorted by relevance for query dojo. Sort by date Show all posts

Wednesday, 2 May 2012

Opportunity Status Chart

An important aspect when working on Opportunities (or anything with a process that a record progresses through) is to knowing how far through the Sales Process a particular Opportunity is.  While some users may be familiar enough with the Sales Process to be able to figure this out immediately from the Stage Name, a visual indicator always helps.

Something I've been working on for a while now is using a line chart for just this purpose.  The key elements of this are:


  • List all of the Stages for the Opportunity - if record types are in use, this should be the Stages applicable to that record type
  • Plot a green line and markers for Stages that have been completed (or skipped, if the Opportunity has jumped into the process at an advanced staged
  • Plot a red line and markers for Stages still to come

The first point had always been a sticking point, but I managed to solve this using back in January by allowing Visualforce to render the specific picklist for the record, and then sending this back to the controller prior to processing the record, as detailed in this post.

It will come as no surprise to regular readers of this blog that I chose to handle the second and third points using the Dojo Charting framework.  I've finally completed the charting code  - below is a screenshot showing the chart embedded into the standard Opportunity record view page:



The controller simply walks the Stage Names and adds them to a complete list until it encounters the current state.   All Stage Names after this go into a todo list - note that both Closed-Won and Closed-Lost appear, and also the default '-- None-- entry - probably not what is desired and left as an exercise for the avid student.  The page is as follows:

<apex:page standardController="Opportunity" extensions="OpportunityStatusChartController" showheader="false" standardstylesheets="false" sidebar="false">
<head>
  <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>
  <link rel="stylesheet"
    href="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css" />
</head>
<body class="claro">
  <apex:form id="frm">
    <apex:actionFunction name="reloadWithStages" action="{!reload}" />
    <div id="test1" style="width: 100%; height: 150px;"></div>
    <apex:outputPanel layout="block" id="vals" style="display:none">
      <apex:inputField value="{!Opportunity.StageName}" required="false" id="stages"/>
      <apex:inputText value="{!valsText}" required="false" id="back"/>
    </apex:outputPanel>
  </apex:form>
  <apex:outputField value="{!Opportunity.StageName}" rendered="false"/>

  <script>
  function reload()
  {
     var ele=document.getElementById('{!$Component.frm.stages}');
     var idx=0;
     var valText='';
     for (idx=0; idx<ele.length; idx++)
     {
        valText+=ele.options[idx].text + ':';
     }
   
     var backele=document.getElementById('{!$Component.frm.back}');
     backele.value=valText;
   
     reloadWithStages();
  }

  <apex:outputPanel layout="none" rendered="{!NOT(loadOnce)}">
    dojo.require("dojox.charting.Chart2D");
    dojo.require("dojox.charting.axis2d.Default");  
    dojo.require("dojox.charting.plot2d.Default");
    dojo.require("dojox.charting.plot2d.StackedLines");
    dojo.require("dojox.charting.plot2d.Columns");
    dojo.require("dojox.charting.plot2d.Bars");
    dojo.require("dojox.charting.plot2d.ClusteredBars");
    dojo.require("dojox.charting.plot2d.StackedBars");
    dojo.require("dojox.charting.plot2d.Bubble");
    dojo.require("dojox.charting.plot2d.Grid");
    dojo.require("dojox.charting.plot2d.Pie");

    dojo.require("dojox.charting.themes.PlotKit.green");

    dojo.require("dojox.charting.action2d.Highlight");
    dojo.require("dojox.charting.action2d.Magnify");
    dojo.require("dojox.charting.action2d.MoveSlice");
    dojo.require("dojox.charting.action2d.Shake");
    dojo.require("dojox.charting.action2d.Tooltip");

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

    dojo.require("dojo.colors");
    dojo.require("dojo.fx.easing");
    dojo.require("dojo.date.stamp");
    dojo.require("dojo.date.locale");

    makeCharts = function(){

 var myMap={
  <apex:repeat value="{!labels}" var="label">
      '{!label.idx}':'{!label.text}'<apex:outputText value="," rendered="{!label.idx!=labelCount}"/>
  </apex:repeat>
            };
            
 var chart1 = new dojox.charting.Chart2D("test1");
 chart1.setTheme(dojox.charting.themes.PlotKit.green);
 chart1.addPlot("default", {type: "Default", lines: true, markers: true, tension:2});
 chart1.addAxis("x",
 { 
    majorTick: {stroke: "black", length: 3},
    majorTickStep:1, 
  minorTicks: false, 
  microTicks: false,
  min: 0,
  max: {!labelCount},
  rotation:30, 
  font: "6pt Tahoma",
  labels: [
  <apex:repeat value="{!labels}" var="label">
      {value: {!label.idx}, text:'{!label.text}'}<apex:outputText value="," rendered="{!label.idx!=labelCount}"/>
  </apex:repeat>
  ]
  
 });
 
 chart1.addSeries("cleared", 
   [
       <apex:repeat value="{!doneStageNumbers}" var="stage">
    {x: {!stage}, y: 2},
    </apex:repeat>
   ]);
 chart1.addSeries("todo", 
   [
       <apex:repeat value="{!todoStageNumbers}" var="stage">
    {x: {!stage}, y: 2},
    </apex:repeat>
   ],
   {plot: "default", stroke: {color:"#FE2E2E"}}
   );
   
 var myMap={
  <apex:repeat value="{!tooltips}" var="tooltip">
   "{!tooltip.idx}": "{!tooltip.text}",
  </apex:repeat>
            };
            
 var anim1a = new dojox.charting.action2d.Magnify(chart1, "default");
 var anim1b = new dojox.charting.action2d.Tooltip(chart1, "default",
 {
     text : function(o) {
         return (myMap[o.x])
                       }
        });
   
 chart1.render();
    };
  </apex:outputPanel>
   dojo.addOnLoad(
     <apex:outputPanel layout="none" rendered="{!loadOnce}">
        reload
     </apex:outputPanel>
     <apex:outputPanel layout="none" rendered="{!NOT(loadOnce)}">
 makeCharts
     </apex:outputPanel>
      );
  </script>
</body>
</apex:page>

Essentially this page has two distinct functions - the first to render a hidden form containing an input field for the StageName field, and then submit the values back via javascript, while the second is to actually render the chart based on the values.  The completed stages are plotted as a separate series to those that remain, to allow that part of the chart to be rendered in a different colour and markers. I've also thrown in a tooltip on each of my markers so that I can display some help/explanation text about each of the stages.

The controller is quite a simple one:
public class OpportunityStatusChartController
{
 public List<Tuple> labels {get; set;}
 public List<Tuple> tooltips {get; set;}
 public List<Integer> doneStageNumbers {get; set;}
 public List<Integer> todoStageNumbers {get; set;}
 public Integer labelCount {get; set;}
 public Boolean loadOnce {get; set;}
 public String valsText {get; set;}
        private Opportunity opp;
 
 public OpportunityStatusChartController(ApexPages.StandardController std)
 {
  opp=(Opportunity) std.getRecord();
  loadOnce=true;
 }
 
 public PageReference reload()
 {
  init();
  loadOnce=false;
  
  return null;
 }
 
 
 public void init()
 {
  labels=new List<Tuple>();
  tooltips=new List<Tuple>();
  doneStageNumbers=new List<Integer>();
  todoStageNumbers=new List<Integer>();
  
  labelCount=0;
  Boolean done=false;
  labels.add(new Tuple(labelCount++, '.'));
  
  for (String val : valsText.split(':'))
  {
   if (!done)
   {
    doneStageNumbers.add(labelCount);
   }
   else
   {
    todoStageNumbers.add(labelCount);
   }
    
   if (val==opp.StageName)
   {
    done=true;
    todoStageNumbers.add(labelCount);
   }
   labels.add(new Tuple(labelCount, val));
   toolTips.add(new Tuple(labelCount, 'Help for ' + val + ' stage'));
   labelCount++;
  }  
  labels.add(new Tuple(labelCount, '.'));
 }
 
 public class Tuple 
 {
  public Integer idx {get; set;}
  public String text {get; set;}
  
  public Tuple(Integer inIdx, String inText)
  {
   idx=inIdx;
   text=inText;
  }
 }
}

The labels are returned in a containing Tuple class along with an index, which is the x-axis position of the ploy.  Everything is plotted at the same level on the y-axis, as I want a straight line chart.

I can then change the record type of the Opportunity, save it and the chart updates accordingly:


You can see a live example of this on my demo site - simply click on one of the Opportunities to see the progress chart.


Wednesday, 21 September 2011

Dojo Charts Part 1 - Pie Charts

Dreamforce and Cloudforce are now over for another year, so its time to blog some code for a change.  This is the first in a short series of posts detailing how to produce charts in Visualforce using the Dojo Javascript toolkit.

I've been using Dojo Charting for around 18 months now on a variety of projects.  I don't propose to go into the minute detail in this post - there's plenty of information on this at the Sitepen site - a good starting point is: A Beginners Guide to Dojo Charting.  This post is concerned with integrating Dojo Charting into Visualforce pages. 

In view of the upcoming Winter 12 release, and the Developer Preview of Visualforce Charting, I'm sure some people will be wondering if there's any value to a Javascript charting solution.  My view on this is that its another tool in the box - plus Dojo Charting is available for production use now.

And so to the code.  The "parent" page provides the hooks into a cross domain build of Dojo hosted by Google.  There is always the option to upload the Dojo library as a static resource into your Salesforce org, but I prefer not to have to worry about managing this resource across every Salesforce org.  This page also contains sample data that is used to generate the sample pie chart.  The source for the page is shown below:

<apex:page controller="DojoPieChartController">
<script 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' 
             }
    " src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" type="text/javascript">
</script>

<apex:sectionheader title="Dojo Pie Chart">
<apex:form>
 <apex:pageblock title="Chart Data">
  <table>
    <tbody>
    <tr>
      <td>Value</td>
      <td>Color</td>
      <td>Text</td>
    </tr> 
    <tr>
      <td><apex:inputtext value="{!value1}">
      </apex:inputtext></td>
      <td><apex:inputtext value="{!color1}">
      </apex:inputtext></td>
      <td><apex:inputtext value="{!text1}">
      </apex:inputtext></td>
    </tr> 
    <tr>
      <td><apex:inputtext value="{!value2}">
      </apex:inputtext></td>
      <td><apex:inputtext value="{!color2}">
      </apex:inputtext></td>
      <td><apex:inputtext value="{!text2}">
      </apex:inputtext></td>
    </tr>
    <tr>
      <td><apex:inputtext value="{!value3}">
      </apex:inputtext></td>
      <td><apex:inputtext value="{!color3}">
      </apex:inputtext></td>
      <td><apex:inputtext value="{!text3}">
      </apex:inputtext></td>
    </tr>
</tbody>
</table>
<apex:commandbutton value="Update">
 </apex:commandbutton></apex:pageblock>
</apex:form>
<c:dojopiechart color1="{!color1}" color2="{!color2}" color3="{!color3}" 
id="piechart" text1="{!text1}" text2="{!text2}" text3="{!text3}" 
title="Example pie chart" value1="{!value1}" value2="{!value2}" 
value3="{!value3}">
</c:dojopiechart>
</apex:sectionheader>
</apex:page>

The real work is done by the dojopiechart component. This takes three sets of parameters, one set per wedge of the pie.  For each wedge, the colour (or color, as Dojo is clearly American!), the text to display in the legend and the value for the wedge - the values should add up to 100.

The component code is shown below.


<apex:component >
<apex:attribute name="value1" type="String" description="The value for slice 1"/>
<apex:attribute name="color1" type="String" description="The colour for slice 1"/>
<apex:attribute name="text1" type="String" description="Text to display against slice 1"/>
<apex:attribute name="value2" type="String" description="The value for slice 2"/>
<apex:attribute name="color2" type="String" description="The colour for slice 2"/>
<apex:attribute name="text2" type="String" description="Text to display against slice 2"/>
<apex:attribute name="value3" type="String" description="The value for slice 3"/>
<apex:attribute name="color3" type="String" description="The colour for slice 3"/>
<apex:attribute name="text3" type="String" description="Text to display against slice 3"/>
<apex:attribute name="divId" type="String" description="ID of the div to store the chart in" default="piechart"/>
<apex:attribute name="title" type="String" description="Title of the chart"/>

<script type="text/javascript">
dojo.require("dojox.charting.Chart2D");
dojo.require("dojox.charting.plot2d.Pie");
dojo.require("dojox.charting.widget.Legend");
dojo.require("dojox.charting.themes.MiamiNice");


makeCharts = function(){

		var chart=new dojox.charting.Chart2D("{!divId}");
        chart.addPlot("default", 
                {type: "Pie",
    	        font: "normal normal 11pt Tahoma",
        	    fontColor: "black",
            	labelOffset: -40,
	            radius: 80
                      });
                     
		chart.setTheme(dojox.charting.themes.MiamiNice);
        chart.addSeries("Series A", [
                  {y: {!value1}, text: "{!text1}", color: "{!color1}"},
                  {y: {!value2}, text: "{!text2}", color: "{!color2}"},
                  {y: {!value3}, text: "{!text3}", color: "{!color3}"}
                  ]);
        
        chart.render(); 
};

dojo.addOnLoad(makeCharts);

</script>
<div style="font-family:Verdana; font-size:14px; font-weight:bold;">{!Title}</div>
<div id="{!divId}" style="width: 300px; height: 300px; margin-left:50px;"></div>
</apex:component>
The dojo.require function calls pull in the Dojo modules that are needed to support generation of the chart. You can learn more about dojo.require at the Sitepen web site.

The chart is rendered into the <div> element identified by the divId attribute of the component  - this allows for multiple pie charts to appear in the same page. The wedges of the pie are supplied via the chart.addSeries function.

Below is a screenshot of the default page:


As the chart is "live", Changing the values in the Chart Data table and clicking Update generates an updated chart based on the new data:


I've made this available as a Google Code project, and also submitted it as a developer force code share, but it looks like it takes a while to get accepted and added to the list of projects.

Sunday, 2 October 2011

Dojo Charts Part 2 - Bar Charts

Following on from last weeks Pie Chart post, this week I'm presenting an example of a Bar Chart generated via the Dojo JavaScript library.

As in the previous example, the Visualforce page pulls in the cross domain Dojo build from Google, has a couple of input fields (so that the data can be updated live) and responsibility for the production of the chart is delegated to a custom component:


<apex:page controller="DojoBarChartController">
<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 Bar Chart"/>
<apex:form >
 <apex:pageBlock title="Chart Data">
   <table>
    <tr>
      <td>Labels:</td>
      <td>
         <apex:inputText value="{!labels}" size="80"/>
      </td>
    </tr>
    <tr>
      <td>Values:</td>
      <td>
        <apex:inputText value="{!series1}"/>
      </td>
    </tr>
  </table>
  <apex:commandButton value="Update"/>
 </apex:pageBlock>
</apex:form>
<c:DojoBarChart id="barchart" minx="0" stepx="2"
			     labelsy="{!yAxisLabels}"   
			     title1="Data" 
			     series1="{!series1}"
                             color1="#000"
                             fill1="blue"
			     />
</apex:page>

The labels and series1 properties are simply comma separated lists of values, where the first entry in the series1 is associated with the first entry in labels and so on.  The controller converts the labels into a suitable format for use in the JavaScript that sets up the bar chart.

The component takes a number of attributes, not all of which are supplied from the page.  As an aside, this is by no means the full functionality available from a Dojo bar chart - there are click throughs, animations and many other features, some of which will be explored in later posts in this series.


<apex:component >
  <apex:attribute name="minx" description="Minimum X Axis value" type="Integer"/>
  <apex:attribute name="maxx" description="Maximum X Axis value" type="Integer"/>
  <apex:attribute name="stepx" description="Step value for X axis" type="Integer"/>
  <apex:attribute name="labelsy" description="Y Axis Labels" type="String" />
  <apex:attribute name="title1" description="Plot series 1 title - displayed in legend" type="String" />
  <apex:attribute name="series1" description="Plotext>
                       vertical: true, 
                       natural: false});
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title1))}">
           chart1.addSeries("{!title1}", [{!series1}],
                {stroke: {color:"{!color1}"}, fill: "{!fill1}"});
        </apex:outputPanel>
        chart1.render();
        <apex:outputText rendered="{!showLegend}">
		var legend1 = new dojox.charting.widget.Legend({chart: chart1, horizontal: false}, "legend1");
		</apex:outputText>        
};

dojo.addOnLoadavascript">

  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: "Bars", gap:10});
        chart1.addAxis("x", {
                       <apex:outputText value="min:{!minx}," rendered="{!NOT(ISBLANK(minx))}"/> 
                       <apex:outputText value="max:{!maxx}," rendered="{!NOT(ISBLANK(maxx))}"/> 
                       <apex:outputText value="majorTickStep:{!stepx}," rendered="{!NOT(ISBLANK(stepx))}"/> 
		});
		
        chart1.addAxis("y", 
                      {
  		   <apex:outputText rendered="{!NOT(ISBLANK(labelsy))}">
            labels:
               [
                 {!labelsy}
               ],
            </apex:outputText>
                       vertical: true, 
                       natural: false});
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title1))}">
           chart1.addSeries("{!title1}", [{!series1}],
                {stroke: {color:"{!color1}"}, fill: "{!fill1}"});
        </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>

Some of the values (minx, maxx) are optional - Dojo will calculate these if you don't provide them.   The creation of the chart follows a simple path - create the chart, add a plot, add x and y axis, add the data series and render the chart.  In this case the chart is rendered into the div with the id of "simplechart".  If multiple charts were required on the same page, this should be passed as an attribute to the component.

The page with the generated chart is shown below:


As before, the chart is live, so updating the values in the Chart Data section and clicking "Update" regenerates the chart with the latest data:


The page, controller and component are available in the Google Code project, along with a number of additional examples.  The next post will cover how to generate a stacked chart.

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:


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

Friday, 10 September 2021

JavaScript for Apex Programmers Part 1 - Typing

Background

When I started working with Salesforce way back in 2008, I had a natural affinity for the Apex programming language, as I'd spent the previous decade working with Object Oriented languages - first C++, then 8 years or so with Java. Visualforce was also a very easy transition, as I had spent a lot of time building custom front ends using Java technologies - servlets first before moving on to JavaServer Pages (now Jakarta Server Pages), which had a huge amount of overlap with the Visualforce custom tag approach. 

One area where I didn't have a huge amount of experience was JavaScript. Oddly I had a few years experience with server side JavaScript due to maintaining and extending the OpenMarket TRANSACT product, but that was mostly small tweaks added to existing functionality, and nothing that required me to learn much about the language itself, such as it was back then. 

I occasionally used JavaScript in Visualforce to do things like refreshing a record detail from an embedded Visualforce page, Onload Handling or Dojo Charts. All of these had something in common though, they were snippets of JavaScript that were rendered by Visualforce markup, including the data that they operated on. There was no connection with the server, or any kind of business logic worthy of the name - everything was figured out server side. 

Then came JavaScript Remoting, which I used relatively infrequently for pure Visualforce, as I didn't particularly like striping the business logic across the controller and the front end, until the Salesforce1 mobile app came along. Using Visualforce, with it's server round trips and re-rendering of large chunks of the page suddenly felt clunky compared to doing as much as possible on the device, and I was seized with the zeal of the newly converted. I'm pretty sure my JavaScript still looked like Apex code that had been through some automatic translation process, as I was still getting to grips with the JavaScript language, much of which was simply baffling to my server side conditioned eyes. 

It wasn't long before I was looking at jQuery Mobile to produce Single Page Applications where maintaining state is entirely the job of the front end, which quickly led me to Knockout.js as I could use bindings again, rather than having to manually update elements when data changed. This period culminated my Dreamforce 2013 session on Mobilizing your Visualforce Application with jQuery Mobile and Knockout.js

Then in 2015, Lightning Components (now Aura Components) came along, where suddenly JavaScript got real. Rather than rendering via Visualforce or including from a static resource, my pages were assembled from re-usable JavaScript components. While Aura didn't exactly encourage it's developers down the modern JavaScript route, it's successor - Lightning Web Components - certainly did.

All this is rather a lengthy introduction to the purpose of this series of blogs, which are intended to (try to) explain some of the differences and challenges when moving to JavaScript from an Apex background. This isn't a JavaScript tutorial, it's more about what I wish I'd known when I started. It's also based on my experience, which as you can see from above, was a somewhat meandering path. Anyone starting their journey should find it a lot more straightforward now, but there's still plenty there to baffle!

Strong versus Weak (Loose) Typing

The first challenge I encountered with JavaScript was the difference in typing. 

Apex

Apex is a strongly typed language, where every variable is declared with the type of data that it can store, and that cannot change during the life of the variable. 

    Date dealDate;

In the line above, dealDate is declared of type Date, and can only store dates. Attempts to assign it DateTime or Boolean values explicitly will cause compiler errors:

    dealDate=true;         // Illegal assignment from Boolean to Date
    dealDate=System.now(); // Illegal assignment from DateTime to Date

while attempts to assign something that might be a Date, but turns out not to be at runtime will throw an exception:

    Object candidate=System.now();
    dealDate=(Date) candidate; // System.TypeException: Invalid conversion from runtime type Datetime to
    Date

JavaScript

JavaScript is a weakly typed language, where values have types but variables don't.  You simply declare a variable using var or let, then assign whatever you want to it, changing the type as you need to:

    let dealDate;
    dealDate=2;               // dealDate is now a number
    dealDate='Yesterday';     // dealDate is now a string
    dealDate=Date();          // dealDate is now a date

The JavaScript interpreter assumes that you are happy with the value you have assigned the variable and will use it appropriately. If you use it inappropriately, this will sometimes be picked up at runtime and a TypeError thrown. For example, attempting to run the toUpperCase() string method on a number primitive:

    let val=1;
    val.toUpperCase();
    Uncaught TypeError: val.toUpperCase is not a function

However, as long as the way you are attempting to use the variable is legal, inappropriate usage often just gives you an unexpected result. Take the following, based on a simplified example of something I've done a number of times - I have an array and I want to find the position of the value 3.

    let numArray=[1,2,3,4];
    numArray.indexOf[3];

which returns undefined, rather than the expected position of 2.

Did you spot the error? I used the square bracket notation instead of round brackets to demarcate the parameter. So instead of executing the indexOf function, JavaScript was quite happy to treat the function as an array and return me the third element, which doesn't exist.

JavaScript also does a lot more automatic conversion of types, making assumptions that might not be obvious.  To use the + operator as an example, this can mean concatenation for strings or addition for numbers, so there are a few decisions to be made:

lhs + rhs

1. If either of lhs/rhs is an object, it is converted to a primitive string, number or boolean

2. If either of lhs/rhs is string primitive, the other is converted to a string (if necessary) and they are concatenated

3. lhs and rhs are converted to numbers (if necessary) and they are added

Which sounds perfectly reasonable in theory, but can surprise you in practice:

    1 + true 

result: 2, true is converted to the number 1

    5 + '4'  

result '54', rhs is a string so 5 is converted to a string and concatenated.

    false + 2

result: 2, false is converted to the number 0

    5 + 3 + '5'

result '85' - 5 + 3 adds the two numbers to give 8, which is then converted to a string to concatenate with '5'

    [1992, 2015, 2021] + 9

result '1992,2015,20219' - lhs is an object (array) which is converted to a primitive using the toString method, giving the string '1992,2015,2021', 9 is converted to the string '9' and the two strings are concatenated

Which is Better?

Is this my first rodeo? We can't even agree on what strong and weak typing really mean, so deciding whether one is preferred over the other is an impossible task. In this case it doesn't matter, as Apex and JavaScript aren't going to change!

Strongly typed languages are generally considered safer, especially for beginners, as more errors are trapped at compile time. There may also be some performance benefits as you have made guarantees to the compiler that it can use when applying optimisation, but this is getting harder to quantify and in reality is unlikely to be a major performance factor in any code that you write.

Weakly typed languages are typically more concise,  and the ability to pass any type as a parameter to a function can be really useful when building things like loggers.

Personally I take the view that code is written for computers but read by humans, so anything that clarifies intent is good. If I don't have strong typing, I'll choose a naming convention that makes the type of my variables clear, and I'll avoid re-using variables to hold different types even if the language allows me to.




Saturday, 27 October 2012

Building a Templated Web Site with Force.com - Part 1

An area of Salesforce that often seems underrated to me is Force.com sites.  If you are an Enterprise or Unlimited Edition customer, you can create up to 25 sites with a total hosting cost of zero down and zero a month. All you need is some Visualforce capability, and maybe Apex if you want to hook the site up with your Salesforce records. 

If you've been following this blog for a while, you'll know there's a couple of web sites that I've built using Force.com sites:

  • http://www.bobbuzzard.org/ - this is a site I use to demonstrate some interesting (hopefully) Force.com functionality, including Dojo charting, an opportunity progression chart and a mobile survey application
  • http://tests.bobbuzzard.org/ - my online tests site, allowing Force.com developers to test their knowledge of various features of the platform.  This has proved quite popular I'm very pleased to say.
My company, BrightGen, also moved our website to Force.com sites earlier this year. For us this decision was more about maximising our capability to maintain the site rather than hosting costs.  As we have a large pool of Apex and Visualforce developers, it means that we aren't reliant on a third party to make changes at short notice.
 
 In this series of posts I'll show you how to build a templated web site from scratch.  

Choosing Your Template

A templated web site simply means that the content that is repeated across all pages (header, footer, sidebar etc) is generated from a template.  Each page on the web site uses this template as its starting point and injects its specific content.  So a contact us page, for example, would have the same header and footer as a news page, but would inject a form that the end user can fill in to make contact.  When we moved the BrightGen web site to Force.com sites, each of the pre-existing pages had the header, footer and sidebar repeated in each page, so we had to spend some time analysing those to build a template that worked for all of them.  For the purposes of my demo site, I'm going to start with a clean sheet which makes it a lot more straightforward.

The first thing you might be tempted to do when building a Force.com site is to dive straight in and configure the site.  However, as the site is based on Visualforce pages, I prefer to get a basic version available before making it available externally.  Thus the first step for me is always to decide on the template that I'm going to use.

If you look closely at either of the Bob Buzzard sites I've mentioned above, you'll see the following text in the footer:

     Design by FCT.

FCT stands for Free CSS Templates and these are free as in beer.  The only requirement is that you link back to the http://www.freecsstemplates.org/ website.  As any template you download will have a link somewhere on the page already, its often just a case of leaving an area of the page alone!  I've gone for the defrost template, as shown below:

 

The templates are downloadable as a zip file. There's not a lot to them, as can be seen by the expanded contents of the file:

 

The index.html is the example page from the template gallery - this is a key file for me as I'll be using this as the basis for my site template, so the first thing I do is to extract this to my local file system.  As I'll be using the images and css on my site, I need to make it available via the Force.com platform, so I upload the zip as a static resource named 'Defrost'.  Make sure to set the Cache Control to Public when uploading the static resource, as this will make your site more performant when you release it into the wild.

Next up I need to convert the index.html page into a Visualforce page.  As I'm going to be using this as my template and I'm exceptionally creative, I've named my page 'template'.  To get started, I paste the contents of the html file into my new Visualforce page:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Design by Free CSS Templates
http://www.freecsstemplates.org
Released for free under a Creative Commons Attribution 2.5 License

Name       : Defrost
Description: A two-column, fixed-width design with dark color scheme.
Version    : 1.0
Released   : 20111121

-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="keywords" content="" />
<meta name="description" content="" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Defrost by FCT</title>
<link href="http://fonts.googleapis.com/css?family=Oxygen" rel="stylesheet" type="text/css" />
<link href="style.css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body>
<div id="wrapper">
	<div id="header-wrapper">
		<div id="header">
			<div id="logo">
				<h1><a href="#">Defrost</a></h1>
				<p>template design by free <a href="http://www.freecsstemplates.org/">FCT</a></p>
			</div>
		</div>
	</div>
	<!-- end #header -->
	<div id="menu">
		<ul>
			<li class="current_page_item"><a href="#">Homepage</a></li>
			<li><a href="#">Blog</a></li>
			<li><a href="#">Photos</a></li>
			<li><a href="#">About</a></li>
			<li><a href="#">Links</a></li>
			<li><a href="#">Contact</a></li>
		</ul>
	</div>
	<!-- end #menu -->
	<div id="page">
		<div id="page-bgtop">
			<div id="page-bgbtm">
				<div id="content">
					<div class="post">
						<h2 class="title"><a href="#">Welcome to Defrost</a></h2>
						<div class="entry">
							<p><img src="images/pics01.jpg" width="600" height="200" alt="" />This is <strong>Defrost</strong>, a free, fully standards-compliant CSS template designed by  <a href="http://www.freecsstemplates.org">FCT</a>.  The picture in this template is from <a href="http://fotogrph.com/">FotoGrph</a>.This free template is released under a <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attributions 3.0</a> license, so you’re pretty much free to do whatever you want with it (even use it commercially) provided you keep the links in the footer intact. Aside from that, have fun with it :)</p>
						</div>
					</div>
					<div class="post">
						<h2 class="title"><a href="#">Lorem ipsum sed aliquam</a></h2>
						<div class="entry">
							<p><img src="images/pics02.jpg" width="600" height="200" alt="" />Sed lacus. Donec lectus. Nullam pretium nibh ut turpis. Nam bibendum. In nulla tortor, elementum vel, tempor at, varius non, purus. Mauris vitae nisl nec metus placerat consectetuer. Donec ipsum. Proin imperdiet est. Phasellus <a href="#">dapibus semper urna</a>. Pellentesque ornare, consectetuer nisl felis ac diam. Sed lacus. Donec lectus. Nullam pretium nibh ut turpis. Nam bibendum. Mauris vitae nisl nec metus placerat consectetuer. </p>
						</div>
					</div>
					<div class="post">
						<h2 class="title"><a href="#">Phasellus pellentesque turpis </a></h2>
						<div class="entry">
							<p><img src="images/pics01.jpg" width="600" height="200" alt="" />Sed lacus. Donec lectus. Nullam pretium nibh ut turpis. Nam bibendum. In nulla tortor, elementum vel, tempor at, varius non, purus. Mauris vitae nisl nec metus placerat consectetuer. Donec ipsum. Proin imperdiet est. Pellentesque ornare, orci in consectetuer hendrerit, urna elit eleifend nunc. Donec ipsum. Proin imperdiet est. Pellentesque ornare, orci in consectetuer hendrerit, urna elit eleifend nunc.</p>
						</div>
					</div>
					<div style="clear: both;">&nbsp;</div>
				</div>
				<!-- end #content -->
				<div id="sidebar">
					<ul>
						<li>
							<h2>Aliquam tempus</h2>
							<p>Mauris vitae nisl nec metus placerat perdiet est. Phasellus dapibus semper consectetuer hendrerit.</p>
						</li>
						<li>
							<h2>Categories</h2>
							<ul>
								<li><a href="#">Aliquam libero</a></li>
								<li><a href="#">Consectetuer adipiscing elit</a></li>
								<li><a href="#">Metus aliquam pellentesque</a></li>
								<li><a href="#">Suspendisse iaculis mauris</a></li>
								<li><a href="#">Urnanet non molestie semper</a></li>
								<li><a href="#">Proin gravida orci porttitor</a></li>
							</ul>
						</li>
						<li>
							<h2>Blogroll</h2>
							<ul>
								<li><a href="#">Aliquam libero</a></li>
								<li><a href="#">Consectetuer adipiscing elit</a></li>
								<li><a href="#">Metus aliquam pellentesque</a></li>
								<li><a href="#">Suspendisse iaculis mauris</a></li>
								<li><a href="#">Urnanet non molestie semper</a></li>
								<li><a href="#">Proin gravida orci porttitor</a></li>
							</ul>
						</li>
						<li>
							<h2>Archives</h2>
							<ul>
								<li><a href="#">Aliquam libero</a></li>
								<li><a href="#">Consectetuer adipiscing elit</a></li>
								<li><a href="#">Metus aliquam pellentesque</a></li>
								<li><a href="#">Suspendisse iaculis mauris</a></li>
								<li><a href="#">Urnanet non molestie semper</a></li>
								<li><a href="#">Proin gravida orci porttitor</a></li>
							</ul>
						</li>
					</ul>
				</div>
				<!-- end #sidebar -->
				<div style="clear: both;">&nbsp;</div>
			</div>
		</div>
	</div>
	<!-- end #page -->
</div>
<div id="footer">
	<p>Copyright (c) 2012 Sitename.com. All rights reserved. Design by <a href="http://www.freecsstemplates.org/">FCT</a>. Photos by <a href="http://fotogrph.com/">fotogrph</a>.</p>
</div>
<!-- end #footer -->
</body>
</html>

Attempting to save this page as-is will result in failure, as it isn't a well formatted Visualforce page.  To fix that I need to wrap the page in an <apex:page> component and change the doctype from an element to an attribute of the <apex:page>.  I also remove the header, sidebar and standard stylesheets as I want the page only to use the defrost styling.  This allows the page to save, but accessing the page shows that the job isn't done yet:

This is because the css and image elements don't have the correct path - they still have relative paths based on the original zip file.  While fixing these up I also take the opportunity to convert them to the equivalent Visualforce elements - <apex:styleSheet> and <apex:image>.  As I have the resources available in my zipped static resource, I use the URLFOR function to access the zip contents.

Here's an example of each before: 

<link href="style.css" rel="stylesheet" type="text/css" media="screen" />
<img src="images/pics01.jpg" width="600" height="200" alt="" />

 and after:

<apex:stylesheet value="{!URLFOR($Resource.Defrost, 'style.css')}"/>
<apex:image url="{!URLFOR($Resource.Defrost, 'images/pics01.jpg')}" alt="" width="600" height="200"/>

Accessing the page again shows that my changes have done the trick and it is now rendering the same as the original index.html (I've included the location bar of my browser just to prove there's no trickery):

The updated version of this page and the defrost zip file are available in the Part 1 directory of the github repository for this blog series at:

https://github.com/keirbowden/blog_force_com_sites

In the next post I'll look at how we can take this page and turn it into the template for our site, and create a home page based on that template.  

 

Saturday, 26 February 2011

Visualforce Field Sets

Here at BrightGen, we've always tended to advise customers that replacing edit pages with Visualforce should be a last resort, as it means coding is required if additional fields are created on the sobject.  With the advent of Field Sets in Spring 11, this becomes much less of an issue.

Field sets are well documented in the Salesforce help, so I won't reproduce any of that here.  Instead, here's an example using field sets to create an edit page with additional Visualforce functionality.

Firstly, I've created two Field Sets on the Account standard object.  The first is for general fields that I'll show at the top of the record:


While the second is for Address-specific fields:


Next I create my Visualforce page. The key markup is as follows:

<apex:pageBlock mode="maindetail" title="Account Edit">
        <apex:pageBlockButtons >
           <apex:commandButton value="Cancel" action="{!cancel}"/>
           <apex:commandButton value="Save" action="{!save}"/>
        </apex:pageBlockButtons>
        <apex:pageBlockSection title="General">
           <apex:repeat value="{!$ObjectType.Account.FieldSets.General}" 
                    var="field">
              <apex:inputField value="{!Account[field]}" />
           </apex:repeat>
        </apex:pageBlockSection>
        <apex:pageBlockSection title="Address">
           <apex:repeat value="{!$ObjectType.Account.FieldSets.Address}" 
                    var="field">
              <apex:inputField value="{!Account[field]}" />
           </apex:repeat>
        </apex:pageBlockSection>
        <apex:pageBlockSection title="Bar Chart">
    <div id="barchart" style="width: 450px; height: 25px;"></div>
 </apex:pageBlockSection>
     </apex:pageBlock>

Using the field set is as simple as accessing it from the $ObjectType global variable and iterating the fields:

<apex:repeat value="{!$ObjectType.Account.FieldSets.Address}" 
           var="field">
      <apex:inputField value="{!Account[field]}" />
   </apex:repeat>

The additional Visualforce functionality is a simple Dojo barchart, which is drawn in by Javascript into the barchart div.

Here's the generated page:


As an Administrator, if I then decide that I'd like to add the Industry field to the page.  I simply edit my General Field Set to add the field to the end of the set, refresh the page, and the new field is present with zero coding effort:


I've already used this in one solution that combines record creation with embedded searching capabilities.

Saturday, 28 January 2012

Record Type Specific Picklist Values

This post is my solution to an issue that seems to have been around for a while - how to get at the picklist values for a particular record type programmatically.  I've been working on a JavaScript chart (using my framework of choice for charting - Dojo) that will show how far through the sales process an opportunity is.

Initially I was using the describe capability for the StageName field, which worked fine for a single record type. However, as soon as I introduced multiple opportunity record types with different sales processes, I hit problems - regardless of the sales process, the full set of picklist values was returned each time, even if I navigated to the field via an opportunity with the appropriate record type.  Some googling and searching on the Developerforce discussion boards seemed to confirm that there isn't a simple way to achieve this.

Clearly the information about which stages are applicable to a record type is available to the Salesforce UI, as the Stage picklist contains the values specific to the record type.  Even better, this is respected when using a Visualforce input field.  Therefore one way of getting at this information for use in a Visualforce controller is to have the page supply it.  Utilizing a technique I've written about before in DML During Initialisation, I created a page that would send the picklist values back to the controller the first time it was loaded, and then display the details thereafter.

The first time that the page is loaded the following outputpanel is rendered:

   <apex:outputPanel rendered="{!loadonce}">
      <apex:form id="frm">
         <apex:actionFunction name="reloadWithStages" action="{!reload}"/>
         <div id="test1" style="width: 100%; height: 150px;"></div> 
         <apex:outputPanel layout="block" id="vals" style="display:none">
            <apex:inputField value="{!Opportunity.StageName}" required="false" id="stages"/>
            <apex:inputText value="{!valsText}" required="false" id="back"/>
         </apex:outputPanel>
      </apex:form>
      <script>
         function reload()
         {
            var ele=document.getElementById('{!$Component.frm.stages}');
            var idx=0;
            var valText='';
            for (idx=0; idx<ele.length; idx++)
            {
               valText+=ele.options[idx].text + ':';
            }
   
            var backele=document.getElementById('{!$Component.frm.back}');
            backele.value=valText;
 
            reloadWithStages();
         }

         window.onload=reload(); 
      </script>
   </apex:outputPanel>
   

This contains a hidden form with an inputfield for the opportunity stage and an input text element that will be used to submit the stage options to the controller. The reload() function is installed as an onload handler, and this extracts the option text, concatenates it into a single string value, populates the input text with this string and then submits the form.
When the page is reloaded, the following outputpanel is rendered - this simply creates a pageblocktable to iterate through the available values:

   <apex:outputPanel rendered="{!NOT(loadonce)}">
      <apex:pageBlock title="Status Values for record type {!Opportunity.RecordType.Name}">
         <apex:pageBlockTable value="{!pickListVals}" var="plVal">
            <apex:column headerValue="Stage">
               <apex:outputText value="{!plVal}"/>
            </apex:column>
         </apex:pageBlockTable>
   </apex:pageBlock>
   </apex:outputPanel>

The controller is shown below - the heavy lifting is done by the reload() method - this parses the string containing the picklist values and stores them in a list property. it also sets the loadonce property to false, to ensure that the page is reloaded once and once only:

public with sharing class RecordTypePickListController 
{
 public List<String> pickListVals {get; set;}
 public String valsText {get; set;}
 public Boolean loadOnce {get; set;}
 private Opportunity opp;
 
 public RecordTypePickListController(ApexPages.StandardController std)
 {
  opp=(Opportunity) std.getRecord();
  loadOnce=true;
 }
 
 public PageReference reload()
 {
  pickListVals=new List<String>();
  Boolean skip=true;
  for (String val : valsText.split(':'))
  {
   if (skip)
   {
    skip=false;
   }
   else
   {
    pickListVals.add(val);
   }
  }

  loadOnce=false;
  
  return null;
 }
}


Here are a couple of screenshots of the page doing its thing for opportunities with two different record types.  The first with just a few of the stages:


and the second with most of them:



For the sake of completeness, the entire page is shown below:

<apex:page standardController="Opportunity" extensions="RecordTypePickListController">

   <apex:outputPanel rendered="{!loadonce}">
      <apex:form id="frm">
         <apex:actionFunction name="reloadWithStages" action="{!reload}"/>
         <div id="test1" style="width: 100%; height: 150px;"></div> 
         <apex:outputPanel layout="block" id="vals" style="display:none">
            <apex:inputField value="{!Opportunity.StageName}" required="false" id="stages"/>
            <apex:inputText value="{!valsText}" required="false" id="back"/>
         </apex:outputPanel>
      </apex:form>
      <script>
         function reload()
         {
            var ele=document.getElementById('{!$Component.frm.stages}');
            var idx=0;
            var valText='';
            for (idx=0; idx<ele.length; idx++)
            {
               valText+=ele.options[idx].text + ':';
            }
   
            var backele=document.getElementById('{!$Component.frm.back}');
            backele.value=valText;
 
            reloadWithStages();
         }

         window.onload=reload(); 
      </script>
   </apex:outputPanel>
   
   <apex:outputPanel rendered="{!NOT(loadonce)}">
      <apex:pageBlock title="Status Values for record type {!Opportunity.RecordType.Name}">
         <apex:pageBlockTable value="{!pickListVals}" var="plVal">
            <apex:column headerValue="Stage">
               <apex:outputText value="{!plVal}"/>
            </apex:column>
         </apex:pageBlockTable>
   </apex:pageBlock>
   </apex:outputPanel>
</apex:page>

And a special thanks to the blogger software error that meant I had to write this post twice! Always save at regular intervals.