Saturday, 30 June 2012

HTML Comments in Visualforce Pages

After seeing the presentation from Wes Nolte at Cloudstock London, I've been playing around with knockout.js in my spare time over the last couple of weeks and am very impressed.  I've been building some apps using JQuery Mobile and an area of concern has been the way that I've been striping HTML through javascript functions in order to update page sections with the latest data - using knockout.js I don't have to worry about this any more as I can bind the data and respond to user interaction via an almost Apex controller-like javascript object.

One of the feature of knockout.js is the ability to iterate an array of data via a foreach.  With HTML containers that naturally contain a repeating body (e.g. TBODY or UL) the data can be bound directly to that element. However, if you have a block of markup that you want to repeat without a container (in my case it was simply a DIV element per entry in the array), you make use of HTML comments to wrap the body.  An example of this is shown below, where there is a single static list item and the rest are generated from an array:

<ul>
<li><strong>Days of week:</strong></li>
 <!-- ko foreach: daysOfWeek -->
 <li>
  <span data-bind="text: $data"></span>
 </li>
 <!-- /ko -->
</ul>

<script type="text/javascript">
function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
   'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
};
ko.applyBindings(new viewModel());
</script>

In this case the <!-- ko foreach: daysOfWeek --> comment indicates that everything between it and the <!-- /ko --> end comment should be repeated for each element in the daysOfWeek array of Strings, and the <span> inside each list element should be populated with the array element via the data-bind="text: $data" attribute.  


After saving this markup in my Visualforce page, I viewed the page to be disappointed by the sight of a single list item with the text 'null' in the embedded span.  As I was green to this framework, my initial assumption was that I'd done something wrong.  Observable elements need to be accessed as a function rather than an attribute, so maybe I needed some additional brackets in there?  That just made things worse.  I then added in some debug to output the size of the array in case I'd made a mistake there, and that showed that there were 7 elements as expected.  To check that there wasn't an issue with the javascript file I'd downloaded, I rewrote it to use the UL element as a container and that worked fine.  Some googling showed that nobody else was having this problem, so it was clearly something that I was doing.  After some more head scratching and unsuccessful tweaks a synapse fired and I remembered having a similar issue when attempting to use CSS conditional comments for IE. These comments are interpreted by IE and ignored by all other browsers, so allow specific code or includes to be executed for the IE browser:
<!--[if IE 6]>
Special instructions for IE 6 here
<![endif]-->

I couldn't remember the exact issue, but I'd ended up writing a controller that inspected the USER-AGENT header as I couldn't make it work in the page.  Once I'd found the notes it came flooding back - Visualforce removes HTML comments.  Checking the source of my rendered page confirmed this - my bounding comments were nowhere to be seen.  Moving the logic to the controller wasn't an option here, so I started looking for a workaround.

My first thought was to place the comments inside outputtext tags with the escape attribute set to false:

<apex:outputText escape="false" value="< -- ko foreach: daysOfWeek -->" />

While this was successful in outputting an HTML comment, the body was replaced with asterisks, hardly helpful:

<-- ********************** -->

I really don't understand why this happens - either leave the comment alone or remove it. Mangling helps nobody and adds unnecessary characters to the page.

After a bit more experimentation I came up with the following notation:

<apex:outputText value="<" escape="false"/>!-- ko foreach: daysOfWeek --<apex:outputText value=">" escape="false"/>

It looks pretty ugly but does the trick.  By the way, if you are thinking that this could be made more readable using a custom component, I thought the same, but custom components get wrapped in a bonus <span> element, which broke things.

Upon changing my example markup to:

<ul>
<li><strong>Days of week:</strong></li>
 <apex:outputText value="<" escape="false"/>!-- ko foreach: daysOfWeek --<apex:outputText value=">" escape="false"/>
 <li>
  <span data-bind="text: $data"></span>
 </li>
 <apex:outputText value="<" escape="false"/>!-- /ko --<apex:outputText value=">" escape="false"/>
</ul>
<script type="text/javascript">
function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
   'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
};
ko.applyBindings(new viewModel());
</script>

Everything started working as expected and my faith in knockout.js was restored.  If you agree that HTML comments should be left in the resulting page, please vote up my idea.



Monday, 4 June 2012

Salesforce Certification

In one of my earlier blog posts in April, I set up a mobile application that allowed readers to take a survey on their phone.  At the time I said that I might even take notice of the responses, and I've written this post because I decided I would!  Big thanks to those that tried the app out and gave me feedback.

One area that cropped up a few times was Salesforce certification, and this is also an topic that has garnered a lot of interest when I've blogged or tweeted about it in the past.

Why Get Certified

There are many good reasons to get certified.  A few that I'm aware of are as follows:



  • Enhance your career prospects One of the ways in which Salesforce Cloud Alliance partners are measured is on the number and type of certifications their staff hold, and as the partner level increases through Silver, Gold to Platinum, the number and types of certifications required increases.  For example, Platinum Partners are required to have at least one Technical Architect on staff (which is why I had to complete this exam at pretty short notice at the beginning of the year!).  
    Thus having the appropriate certifications will definitely make you a more interesting candidate and most partners that I am aware of seem to be permanently recruiting these days.  Here at BrightGen,
    we are always interested in the right people.

  • Gain Credibility with Customers In my experience, while this isn't something that customers are particularly aware of initially, it is generally well received when you list your certifications and explain the study and effort that you've had to put in to gain them, and your opinion carries more weight.  Obviously this can vary - for example, one prospect didn't seem to be particularly excited about my extensive list of certifications, but when they handed me their business card, mentioning a PhD and membership of a number of chartered institutes, I began to understand why.

  • Validation Its a great feeling when you see the "Congratulations" message after submitting the exam. Moreover, you have demonstrated the appropriate level of knowledge and ability to earn that certification, which means that you must know something about it!  You can take comfort from that when you find yourself struggling or doubting your decisions.
  • Special Events This is something specific to partners rather than all certified professionals so doesn't apply to everyone.  Events such as Spark the Social Enterprise give excellent opportunities to network with your peers and gain access to key Salesforce experts.  
  • Because its there This was the motivation for me for the advanced admin and advanced developer exams.  Once I'd got those that were required from partnership perspective, I decided that I didn't want to stop until I'd got them all.  As I had the original Consultant certification which was discontinued in March 2012, I heaped additional pressure on myself to get all eight while that was still possible.

Resources

There’s a wealth of information on the internet regarding Salesforce Certification.  As is usually the case with these things, not all of it is useful!  My favourites are:
  • Salesforce.com Certification Facebook Page
    This is where the latest news will break regarding assignment windows and exam status.  They also have regular ‘office hours’ Q&A sessions with their certified experts, which can have really useful information.
  • LinkedIn
    There are a number of useful study and discussion groups on LinkedIn.  The Salesforce.com Certification team also post information and respond to questions, so its well worth following them.
  • Force Certified
    The grandaddy of them all.  Great work by John Coppedge.

Studying

The starting point for me with any exam is the Study Guide.  This gives you details of the purpose and format of the exam, and then a point by point summary of the areas that you will be tested on and how much they count towards the overall mark.  

What happens next really depends on how you like to study - if you find videos the best way, the premier online training catalog has great material, including knowledge check questions throughout to see if any of it is sticking.  If you don’t have access to that, there’s a huge amount on the Salesforce channel on youtube.  

Personally, I’m a big fan of the written word, so I’ll usually head to the online help and go straight to the Printable Tip Sheets and User Guides section.  This is a collection of short documents that take you through setting up and using various aspects of the system.  Its basically the same information as can be found elsewhere in the help, but each document is standalone and takes you through planning, configuration, rollout and day to day use.  I find that reading through these documents while actually setting up and configuring a developer org really helps things stick in my mind.  I also tend to scribble my own highly distilled notes at the same time, then write these up into a short ‘pass notes’ style document that I use to refresh my memory in the run up to the exam.

The Exam

After all the hard work and studying is done, it’s exam time.  I’ve been taking the online proctored versions since they became available, as I much prefer sitting in my own house to travelling to my nearest testing centre in London.  The slight downside to this is that the setup is all my problem.  Clearing everything out of my study and setting the webcam up always takes longer than I expect, so if you are going this route make sure you allow plenty of time.  Also make sure that your webcam and microphone are working well in advance, so that you don’t find yourself rooting through boxes in the attic for your old microphone 10 minutes before the exam is due to start, which is exactly what happened to me the first time!

For most of the exams, the format is similar - a set number of multiple choice questions to be completed in a time limit.  The number of questions, time limit and passing grade differs, but all three increase as you move up the certification food chain.

Below are my tips when taking the exam:
  • Make sure your webcam is set up exactly as required.  I’ve not had any issues with this, but I know people that have and by all accounts it’s pretty annoying and distracting to have the exam freeze and need to call the US.
  • Read the question carefully.  Its really easy to miss the “not”, “doesn’t” or “excluding” and thus end up giving the perfect wrong answer.
  • If you don’t know the correct answer, try to derive it by excluding the wrong answers.  As Sherlock Holmes said: “When you have eliminated the impossible, whatever remains, however improbable, must be the truth.
  • If you don’t know the answer and can’t derive it, mark it for review and move on.  A lot of the time it will come to you when you are thinking about another question.  Very occasionally a later question will be asked in such a way that it will guide you towards the correct answer for an earlier question.
  • If you have access to a pencil and paper, note down how confident you are in the answer.  Based on the passing grade for the exam, it gives a good indication of the pass/fail likelihood.  If you’ve got a high level of confidence in most of the answers, you don’t need to sweat that there are a couple of guesses in there.  Of course the reverse is also true - if they are all guesses its going to be a bumpy ride.
    I give mine a percentage rating:
    • 100% - definitely correct, in my mind anyway
    • 75% - pretty sure it’s correct, but wouldn’t hurt to check later
    • 50% - I’ve narrowed it down to a couple, one of which is definitely the answer
    • 25% - one sounds more plausible than the others
    • 0% - total guess

Share the Love

After you’ve taken the exam, share your experience with the wider community.  Even if you didn’t pass, you may be able to serve as an example for others!

Once you have your certification, you may become the subject matter expert for that area within your company.  Organize some presentations or study group sessions to pass on your knowledge to your colleagues.  This is something I’ve done a fair bit of at BrightGen, and it really works.  Start out with a general heads up about the topic, then capture feedback at the end of each session about what the attendees would like to cover next time and before long you’ll have a team of certified experts and a rich library of collateral.

Write a blog post on your certification journey.  Try to put your personal spin on it, rather than just repeating topic areas from the Study Guide. Was it harder or easier than you thought?  Were there areas that cropped up a lot so you really need to know those in depth? How did you prepare for it, and if you did it again would you try a different approach?

Don’t Game the System

Once you start sharing information, you are likely to start receiving requests to share dumps or sample questions.  Don’t.  You’ve put all that hard work into it, why should someone else get an easy ride?

Don’t try to game the system yourself either.  While having the credential might get through the door for a job, you’ll quickly be found out if you haven’t earned it.   I can’t think of anything worse than being presented to a customer as an expert in the field and then being unmasked as a chancer.  The Salesforce community is also tightly knit, so if you do this the word is likely to spread.

The Sales and Service cloud certifications were withdrawn in April 2012 and have just returned to beta, due to reports of the question and answer set being publicly available.  This is a poster child for the damage that gaming the system can do. Many innocent parties who studied hard for the exam suddenly had their booking cancelled due to a few guilty ones who wanted to cheat their way to success.  In some ways this can be viewed as a backhanded compliment - if the credential wasn’t worth having, people would be trying to cheat their way through, and if it was easy to achieve they wouldn’t need to cheat.


Cloudstock Slide Deck


I had this blog post written a few weeks ago, but then an opportunity to present at Cloudstock came up so it had to go on hold so that I didn't pre-empt my talk. My slide deck from that session is available for download here.

Monday, 14 May 2012

Cloudstock London 2012


In an unexpected turn of events, I'm going to be presenting a community session at Cloudstock London 2012.  Unexpected as I hadn't planned on this, but a slot came open at the last minute and I agreed to fill in.

The upside to this is that I didn't have much time to think about what I was committing to.  The downside was that I had to decide on my topic and produce a description in a few hours.  What to speak about then?   It had to be something I was familiar with that wouldn't take a huge amount of time to prepare, as Cloudstock was just over a week away.  Looking at the list of existing community sessions, there was a theme of deep dives into specific technical areas - @wesnolte on Javascript Remoting and MVC Frameworks for example, or @pbatisson on Advanced Force.com Testing Techniques.

It struck me that there was an opportunity here to reach those attendees with little or no experience of Force.com, or those with some experience looking to progress.  There was already a breakout session introducing the Force.com platform, so there was little point in a session with the same technical angle.  Therefore I decided to look at this from a different perspective, and cover starting out as a Force.com developer, what employers are looking for and career progression.

This seemed like a good starting point, but unlikely to fill a 30 minute session unless I spoke very slowly! As followers of this blog know, if there's one thing I'm pretty familiar with that's Salesforce.com Certification. Most Salesforce/Force.com careers will involve gaining certifications - its certainly a key metric for Cloud Alliance Partners - so I'll be talking about the benefits, what the exams involve and how to prepare.  Since I gained the Technical Architect accreditation I've had a lot of interest in the exam, particularly the Review Board, so I'll be covering that too and expecting questions!

So if you are thinking about a job in Force.com development, or looking to gain recognition and advance your career through Certification, join me at Cloudstock from 12:30 to 1pm to find out more.

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.


Sunday, 22 April 2012

Mobile Apps with Visualforce and JQuery Mobile

I've been spending some of my time building mobile functionality recently.  One thing that has put me off in the past has been the multitude of devices that need to be supported if I want to maximize usage.  Building the same functionality for iOS, Android and others doesn't fill me with joy.

HTML5 has the "write once, run anywhere" capability that first attracted me to Java back in JDK 1.0 days. The downside to this it that it does reduce the functionality available.  For example, offline storage is problematic at the moment with different browsers supporting different standards, and the standards themselves being subject to change, while access to native functionality is still in very early days (Android makes the camera available through javascript but there aren't many other examples out there).   It very much feels like the future though, so I've been heading down that route.

My first foray into developing a mobile front end was for our BGFM product for Dreamforce 2010.  This was simply some Visualforce pages sized and styled appropriately for the iPhone.  The obvious downside to that was that a device with any other form factor needed separate pages (or at least pages that could adjust themselves appropriately).  However, towards the middle of 2011 I came across JQuery Mobile (JQM) and starting using the 1.0 alpha releases.  The great thing about JQM is that it takes care of the cross device side of things, leaving you with one app that works across all popular smartphones, tablets etc.  Even better, it will also work against older devices, dropping back down to basic HTML if necessary.  For details, documentation, tutorials and samples, check out the JQuery Mobile site.

Building Visualforce pages that leverage JQM is pretty straightforward.  Lifting from the getting started page from the JQM site:

<apex:page showHeader="false" sidebar="false" standardStyleSheets="false">
<html> 
    <head> 
    <title>My Page</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1" /> 
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
    <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
    <script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
</head> 
<body> 

<div data-role="page">

    <div data-role="header">
        <h1>My Title</h1>
    </div><!-- /header -->

    <div data-role="content">   
           <ul data-role="listview" data-inset="true" data-filter="true">
          <li><a href="#">Acura</a></li>
          <li><a href="#">Audi</a></li>
          <li><a href="#">BMW</a></li>
          <li><a href="#">Cadillac</a></li>
          <li><a href="#">Ferrari</a></li>
           </ul>
    </div><!-- /content -->

</div><!-- /page -->

</body>
</html>
</apex:page>

Opening this in a mobile device gives:




















As you can see from the code, I've had to do nothing to style this appropriately for the device, simply giving the unordered list a data-role of listview means that JQM takes care of all the heavy lifting. Adding buttons for simple navigation is also straightforward, and JQM provides some transitions to mimic native apps. Again, its all taken care of by the markup:

<a href="index.html" data-role="button" data-inline="true">Cancel</a>
<a href="index.html" data-role="button" data-inline="true" data-theme="b">Save</a>

resulting in a couple of appropriately styled buttons at the bottom of the page:





















Where things get a little more interesting is if you have a form in the page and the buttons are submitting the form rather than simply moving to a new page, as is the case in most of the Visualforce pages I write.

In that case I'd like to use an <apex:commandLink> component, but I can't provide the additional attributes to lay things out correctly - e.g. data-inline="true". While I could write some Javascript to take care of this, in the first instance I'm trying to keep things simple, so I use an <apex:actionFunction> component and tie this to the JQM specific link via an onlick handler as follows:

   <apex:form id="jsfrm">
    <apex:actionFunction action="{!save}" name="save"/>
          .....
      <a href="#" data-role="button" data-inline="true" 
                onclick="$.mobile.showPageLoadingMsg(); save()">Save</a>
          .....
   </apex:form>

The $.mobile.showPageLoadingMsg(); function call simply displays a spinner to let the user know something is happening.

 Standard <apex:inputField/> components also render well most of the time - sometimes I end up using the input text/textarea/checkbox etc to allow for a greater level of control, but a basic form can be created with the minimum of effort.  For example, a simple form to create an account by specifying its name and industry only requires the following markup:

<apex:page showHeader="false" sidebar="false" standardStyleSheets="false" standardController="Account">
<html> 
    <head> 
    <title>Create Account</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1" /> 
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
    <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
    <script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
</head> 
<body> 

<div data-role="page">

    <div data-role="header">
        <h1>Create Account</h1>
    </div><!-- /header -->

    <div data-role="content">   
      <apex:form id="jsfrm">
        <apex:actionFunction action="{!save}" name="save"/>
        <apex:outputLabel for="name" value="Name"/>
        <apex:inputField id="name" value="{!Account.Name}"/>
        
        <apex:outputLabel for="industry" value="Industry"/>
        <apex:inputField id="industry" value="{!Account.Industry}"/>
        
        <a href="index.html" data-role="button" data-inline="true">Cancel</a>
        <a href="index.html" data-role="button" data-inline="true" data-theme="b" 
                   onclick="$.mobile.showPageLoadingMsg(); save();">Save</a><br />
      </apex:form>
     </div><!-- /content -->

</div><!-- /page -->

</body>
</html>
</apex:page>

Produces the page shown below - not too bad for a few lines of markup:




















Obviously once the record is saved, the user is redirected to the standard view page which looks pretty awful on a phone, so standard controllers probably aren't going to be a silver bullet for this, making it the usual challenge for editions below enterprise.

I've put together a demo application that allows a contact to take a survey. Its hosted on an unauthenticated Force.com site so is publicly available.  A couple of sample screen shots are shown below:



If you'd like to try out the demo application, simply click here.

A couple of points to note:

  • I've put this together over a few weekends so I wouldn't be in the least bit surprised if there were some glitches waiting.  
  • I've only tested it with an iPhone and webkit browser, though I can't see why there would be issues with different devices.
  • This is a real functioning application, so the feedback will be stored in my Salesforce instance - so if you feel like leaving real feedback, I may even act on it!

Friday, 6 April 2012

Re-order Formula Report Columns

A non-Visualforce/Apex blog for a change this week. I was working on some reports for our Salesforce project management application, and needed to create a number of summary formula columns. Having created the first three, I wanted to change the order that these formula columns appeared in the report, and was surprised to find that this isn't supported through the UI - if you remove all formula columns from the report and add them back in, they are displayed in the order that they were created.   Searching on the Salesforce Idea Exchange only threw up this open idea.

Below is an example report I've put together to demonstrate this using Accounts.  I've added a single formula column that totals the account employees based on the country:
















I then add a column to total the Annual Revenue, which displays to the right of the Country Employees column:














If I then decide that I'd like the Country Revenue column displayed first, the only way to achieve this through the UI is to delete the formula columns and recreate them in the desired order.  Maybe acceptable when there are only a couple of formula columns, but not an option once there's more than a handful.

Next up was to turn to the metadata API via the Force.com IDE.  As I'd just gone with the defaults when setting up the project, the only metadata components I had were Classes, Pages, Components and Triggers.  Right-clicking (or ctrl-click as I am now a Macbook Air user) the project name brings up the context menu for the project - selecting the Force.com menu followed by the Add/Remove Metadata Components brings up the following dialog:



















Clicking the Add/Remove button (eventually) brings up a list of the available metadata.  Reports appears so I tick the entry for my report:
























and click ok as many times as it takes to close the various dialogs.  When prompted, I select Yes to refresh the project.  Once the revised components have been pulled down from the server, opening the file associated with my report shows :

    <aggregates>
        <calculatedFormula>EMPLOYEES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA1</developerName>
        <isActive>true</isActive>
        <masterLabel>Country Employees</masterLabel>
        <scale>2</scale>
    </aggregates>
    <aggregates>
        <calculatedFormula>SALES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA2</developerName>
        <downGroupingContext>ACCOUNT.ADDRESS1_COUNTRY</downGroupingContext>
        <isActive>true</isActive>
        <masterLabel>Country Revenue</masterLabel>
        <scale>2</scale>
    </aggregates>

Looking promising - not only can I see the formula columns but they also have developer names that imply some sort of ordering. First up I tried changing the order that the columns appear in the XML and saving that. Unfortunately, this change was simply reverted when the file was changed.

Next, I changed the developerName for each column - changing the Country Employees to FORMULA2 and Country Revenue to FORMULA1. Looking better once the save completed - the ordering of the columns in the file had been reversed as shown below:

    <aggregates>
        <calculatedFormula>SALES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA1</developerName>
        <downGroupingContext>ACCOUNT.ADDRESS1_COUNTRY</downGroupingContext>
        <isActive>true</isActive>
        <masterLabel>Country Revenue</masterLabel>
        <scale>2</scale>
    </aggregates>
    <aggregates>
        <calculatedFormula>EMPLOYEES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA2</developerName>
        <isActive>true</isActive>
        <masterLabel>Country Employees</masterLabel>
        <scale>2</scale>
    </aggregates>

Encouraging, but the acid test is to re-run the report:






















Success!  Comparing with the report screenshot above, the Country Revenue and Country Employees columns have changed places.  Not quite a simple as it might be through the UI, but certainly a lot quicker than deleting and recreating formula fields!



Saturday, 24 March 2012

Create Parent and Child Records in One Insert Call

If you're anything like me, creating parent and child records in Apex is something that has to be done on a pretty regular basis. This blog post is one I've had on the list for a while, but it came up on the discussion boards yesterday (March 2012) so it seemed like as good a time as any to write it up properly.

The simplest way to achieve this is to insert the parent, then set the lookup relationship id on the child to the parent record id, as follows:

Account acc=new Account(Name='Blog Acc1');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', AccountId=acc.id);
insert cont;

the downside to this approach is that it is unlikely to scale.  If I need to insert a large number of accounts and contacts, I'll have to manage the relationships myself in some way - probably using wrapper classes to  combine an account and its list of contacts, with multiple iterations to insert the accounts, then populate the lookup fields.  All in all quite a lot of code.

The next avenue I explored was setting the child relationship field to the parent record:

Account acc=new Account(Name='Blog Acc2');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc);
insert cont;

No dice on this I'm afraid - even though I've inserted the parent account first, the child contact is stored without a parent account.

One of the techniques that I came across while studying for the Technical Architect Certification was to specify the parent object via an external id.  So I created an external id field on my account named Master_Id__c and inserted an account:

Account acc=new Account(Name='Blog Acc3', Master_Id__c='Blog Acc3');
insert acc;

Once the account is in place, I can instantiate the parent record based on the external id and set the relationship field:

Account acc=new Account(Master_Id__c='Blog Acc3');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc);
insert cont;

Looking better, as I don't have to set ids, but I'm still inserting the parent first and then the child. Perhaps its possible to instantiate a new account and contact and then insert them later:

Account acc=new Account(Name='Blog Acc 4', Master_Id__c='Blog Acc 4');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc);
insert acc;
insert cont;

Once again, no dice. The same result as the second attempt - the account and contact are inserted, but the relationship is lost. Given that there's very little difference between this and the last attempt, it looks like its the name that is causing the problem. After trying a few permutations, the following code confirmed that this is the case:

Account acc=new Account(Name='Blog Acc 6', Master_Id__c='Blog Acc 6');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Name='Blog Acc 6', Master_Id__c='Blog Acc 6'));
insert cont;

This throws the following exception - System.DmlException: Insert failed. First exception on row 0; first error: INVALID_FIELD, More than 1 field provided in an external foreign key reference in entity: Account: [].
 I've no idea why this exception isn't thrown when I specify a previously instantiated account rather than instantiating as part of the contact record, but there it is.   Given this error message, it seemed possible to create a contact and identify the account by Name, but that didn't work either - a similar exception complaining that Name isn't an external id or indexed field.

 This did guide me to the preferred solution though - simply instantiating a new account as part of the contact and only specifying the external id:

Account acc=new Account(Name='Blog Acc 7', Master_Id__c='Blog Acc 7');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Master_Id__c='Blog Acc 7'));
insert cont;

Looking a lot better, but there are still two insert statements - luckily insert statements can take a list of generic sobjects to insert, so I can insert both objects in one go. As long as the parent record is inserted first, everything works as expected:
Account acc=new Account(Name='Blog Acc 8', Master_Id__c='Blog Acc 8');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Master_Id__c='Blog Acc 8'));

insert new List<Sobject>{acc, cont};