Sunday 23 December 2012

Mobile Quiz Pages

A few months ago, around the time of Dreamforce, I built a Force.com online quiz application.  One of the items on my todo list has been to provide a mechanism to take a quiz via a mobile device.  As I had some spare time in the run up to Christmas this weekend, I've built the first version of this.

I've taken a simplistic approach to this and simply created mobile versions of the various site pages using JQuery Mobile, using the techniques described in one of my earlier blog posts.

Signing up for a test using the latest version of the app at http://tests.bobbuzzard.org will include a QR code in the confirmation page and email, and also a direct link in the email, as shown below:

 

Screen Shot 2012 12 23 at 16 26 49

scanning the QR code takes you to the mobile start page:

Screen Shot 2012 12 23 at 16 31 49

Navigation is much the same as the full site, with the slight change that the buttons appear at the top of the page.  The question pages have the same functionality as the full sight, allowing percentage confidence and notes/feedback to be provided:

Screen Shot 2012 12 23 at 16 32 43  Screen Shot 2012 12 23 at 16 33 28

The View All page displays a list of the actual answers selected, rather than the a,b,c question indices.  The percentage confidence and lack of answers are flagged as before:

Screen Shot 2012 12 23 at 16 33 59Screen Shot 2012 12 23 at 16 40 10

and clicking on any of the list entries takes you back to the question, with the percentage confidence header:

Screen Shot 2012 12 23 at 16 34 11

As I mentioned earlier, this is a simplistic solution.  Its quite slow, as the view state is being transferred backwards and forwards to the server, and the JQuery Mobile transitions can't be used as the mobile browser is being redirected between pages rather than using Ajax.  Javascript Remoting is a better solution for the server side interaction than using Visualforce forms, in my opinion, but that's a topic for another post.

Happy Christmas to all my readers - hope you find the mobile pages useful and be sure to let me know if you hit any problems. 

Saturday 15 December 2012

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

In Part 1 of this series, I looked at how to turn an HTML page into a Visualforce page in order to use it as a template for a Force.com site. Part 2 covered how to turn the page into a template and create a home page that used the template to provide the common content.  Part 3 explained how to expose the pages to the world via an unauthenticated Force.com site.

Thus far I've focused on a single page, but the site has a number of tabs that are supplied by default in the template - Blogs, Photos, About, Links and Contacts.  For the purposes of this blog post, I'm going to create two additional pages - About and Contacts.

As I already have a home page that uses the template, I've cloned this to create new visualforce pages named 'about' and 'contact'.  These contain minimal content.

The contact page:

Screen Shot 2012 12 15 at 11 53 30

and the about page:

Screen Shot 2012 12 15 at 11 53 44

Next I need to create a way to navigate to the pages.  While the tabs contain links, these are empty anchor tags, as can be seen from the template markup:

<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>

Changing the links to point to my Visualforce pages might seem as simple as replacing the '#' with the Visualforce page link.  Unfortunately, Force.com sites need the page to be specified as '/contact', while if the pages are accessed from the Salesforce UI it needs to be specified as '/apex/contact'.  For this reason, its best practice to use the $Page global variable - this will generate the appropriate link based on the user's context.  As I've created pages for the Homepage, About and Contact tabs, the new markup is:

<div id="menu">
	<ul>
		<li class="current_page_item"><a href="{!$Page.Home}">Homepage</a></li>
		<li><a href="#">Blog</a></li>
		<li><a href="#">Photos</a></li>
		<li><a href="{!$Page.about}">About</a></li>
		<li><a href="#">Links</a></li>
		<li><a href="{!$Page.Contact}">Contact</a></li>
	</ul>
</div>

The links now work correctly, but the Homepage tab is always the one highlighted due to the class="current_page_item" attribute. This markup exists in the template, so I need find a way to identify the actual page and set the attribute on the appropriate tab.  

I could write a Visualforce controller for the page, but I'm trying to avoid that at this point and build the site entirely in Visualforce.  I could also add some markup to determine the name of the current URL, and highlight the tab that matches.  The downside to that is if I change the purpose of a page, I have to update the template.  Ideally I'd like to define the tab that should be highlighted in the underlying page, and make the template responsible for the rendering only.

The way that I've handed this is to set the tab name into a variable using the <apex:variable/> component, and then conditionally render the class name based on this variable.

In the underlying pages, its simply another define component - here's the markup from the about page:

<apex:define name="tabdef">
	<apex:variable var="tab" value="about"/>
</apex:define>

there's a little more to do in the template page - I have to define an initial value for the 'tab' variable, otherwise I can't use it elsewhere in the page - I've defaulted to the Homepage tab - that way if I have pages that don't belong to a particular tab, the leftmost will be highlighted.  Then I have to include the value from the underlying page (if this isn't present the default will be used) and finally check the value of tab variable when rendering each tab element.

<div id="menu">
	<apex:variable var="tab" value="home" />
	<apex:insert name="tabdef" />
	<ul>
		<li class="{!IF(tab=='home', 'current_page_item', '')}"><a href="{!$Page.Home}">Homepage</a></li>
		<li><a href="#">Blog</a></li>
		<li><a href="#">Photos</a></li>
		<li class="{!IF(tab=='about', 'current_page_item', '')}"><a href="{!$Page.about}">About</a></li>
		<li><a href="#">Links</a></li>
		<li class="{!IF(tab=='contact', 'current_page_item', '')}"><a href="{!$Page.Contact}">Contact</a></li>
	</ul>
</div>

Looking at my contact page, now, the correct tab is highlighted:

Screen Shot 2012 12 15 at 12 30 49

All that remains is to add the new Visualforce pages to my Force.com site.  On the site setup page (Setup -> App Setup -> Develop -> Sites and click through the site label), select the Edit button on the Site Visualforce Pages section:

Screen Shot 2012 12 15 at 12 32 50

and then add the new pages via the dialog and click the 'Save' button:

Screen Shot 2012 12 15 at 12 36 49

Navigating to the about page via the site's custom URL, shows that all of my new functionality has been made available to the site:

 

Screen Shot 2012 12 15 at 12 38 05

The template, pages and static resources are available in the Part4 directory of the github repository for this blog series at:

https://github.com/keirbowden/blog_force_com_sites

This post is probably the final one in this series, unless another topic occurs to me or is suggested in the comments.

 

Saturday 17 November 2012

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

In Part 1 of this series, I looked at how to turn an HTML page into a Visualforce page in order to use it as a template for a Force.com site.  Part 2 covered how to turn the page into a template and create a home page that used the template to provide the common content.  

Up to now the pages have only been available to users that have logged in to the Salesforce system. This post will show how to expose the pages in a Force.com site, to allow access from the wider web.

Before creating a Force.com site I have to choose my domain name - so I navigate to Setup -> App Setup -> Develop -> Sites and the following page is presented:

Screen Shot 2012 11 17 at 11 30 13

As I'm using a Force.com Free Edition org (yes, I was lucky enough to be able to grab a couple of these when they were available - I only wish I'd had the foresight to grab about 50!) I can't change the rather ugly default domain name, but in Enterprise/Unlimited Edition you can choose what you like, as long as someone else hasn't taken it.  I'm not stuck with this domain name however, as I can define a custom 'vanity' URL for my site, which I'll set up later.

Ticking the box and confirming that I understand I can't change the domain name, registers my domain and takes me to the back to the Sites page with my domain defined.

Screen Shot 2012 11 17 at 11 35 02

I then click the 'New' button to create my new site.  As this is for demo purposes only, I've filled in the minimum amount of information.  If you'd like more information on the purpose of each of these fields, check the help page by clicking the 'Help for this Page' link at the top right.

Screen Shot 2012 11 17 at 11 40 27

As I have specified my 'home' Visualforce page, this is automatically included in the list of pages for the site:

Screen Shot 2012 11 17 at 11 43 59

As an aside, whenever you add a Visualforce page to a site, your site automatically gets access to any Apex classes that it depends on. 

And that's it!  I can now navigate to the default site address and access my page without going through any form of authentication.

Screen Shot 2012 11 17 at 11 48 47

While my site is now available on the internet, the chances of anyone bothering to type in the full URL are pretty slim, so the next task is to set up the vanity URL. This has to be a CNAME redirect from my personal domain to the Force.com site domain name.  In my case I'm going to create a subdomain to bobbuzzard.org of example.bobbuzzard.org.  My registration system is Freeparking, and I have to set up an alias to the Force.com A record in order for the CNAME record to be created:

Screen Shot 2012 11 17 at 12 09 25

I can then verify that my record has been created using an online nslookup tool:

Screen Shot 2012 11 17 at 12 18 09

 

Finally, update the site configuration to define the custom URL:

Screen Shot 2012 11 17 at 14 52 53

Sometimes the DNS records don't propagate for a while, so if the vanity URL doesn't work, give it a couple of hours and try again.

Accessing my site via http://example.bobbuzzard.org shows that everything is set up correctly and propagated:

 

Screen Shot 2012 11 17 at 14 56 01

In the next post I'll add pages for one or two of the other tabs and show one way to highlight the tab associated with the page.

 

 

Saturday 3 November 2012

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

In Part 1 of this series, I looked at how to turn an HTML page into a Visualforce page in order to use it as a template for a Force.com site.  This post covers how to turn the page into a template and create a home page that uses the template to provide the common content.

Visualforce supports templating via a combination of three tags:

  • <apex:insert />
    This tag appears in the template page and is used to inject the content specific to the page actually being rendered. 
  • <apex:composition/>
    This tag appears in the home page and defines the template page that will provide the common content.
  • <apex:define/>
    This tag appears in the home page and defines the content to be injected into the template.  For each define element, there must be an associated <apex:insert/> tag with the same name present in the template page.

My page has the following common content:

Header

Screen Shot 2012 11 03 at 14 15 42

Sidebar:

Screen Shot 2012 11 03 at 14 17 19

and footer

Screen Shot 2012 11 03 at 14 18 03

while the home page has the specific article content:

Screen Shot 2012 11 03 at 14 20 12

Thus my template page needs a single <apex:insert/> component for the article body.  However, I also want to be able to define the title on a per-page basis, so I'll need to add another for that.

Looking at the current source of my page, the following two elements need to be moved out to my home page and replaced with <apex:insert /> tags:

<title>Defrost by FCT</title>

and

<div id="content">
	<div class="post">
		<h2 class="title"><a href="#">Welcome to Defrost</a></h2>
		<div class="entry">
			<p><apex:image url="{!URLFOR($Resource.Defrost, 'images/pics01.jpg')}" alt="" width="600" height="200"/>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><apex:image url="{!URLFOR($Resource.Defrost, 'images/pics02.jpg')}" alt="" width="600" height="200"/>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><apex:image url="{!URLFOR($Resource.Defrost, 'images/pics01.jpg')}" alt="" width="600" height="200"/>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 -->
 

These become: 

<title><apex:insert name="title"/></title>

and

<apex:insert name="content" />
 

I can then create my home page, containing the <apex:composition/> component that defines my template, and the <apex:define/> tags for the title - 'Bob Buzzard Blog Site - Home' and the article content:

<apex:page sidebar="false" showheader="false" standardstylesheets="false">
  <apex:composition template="template">
    <apex:define name="title">
       Bob Buzzard Blog Site - Home
    </apex:define>
    <apex:define name="content">
		<div id="content">
			<div class="post">
				<h2 class="title"><a href="#">Welcome to Defrost</a></h2>
				<div class="entry">
					<p><apex:image url="{!URLFOR($Resource.Defrost, 'images/pics01.jpg')}" alt="" width="600" height="200"/>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><apex:image url="{!URLFOR($Resource.Defrost, 'images/pics02.jpg')}" alt="" width="600" height="200"/>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><apex:image url="{!URLFOR($Resource.Defrost, 'images/pics01.jpg')}" alt="" width="600" height="200"/>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 -->
    </apex:define>
  </apex:composition>
</apex:page>

 accessing my home visualforce page shows that the template has been correctly picked up:

Screen Shot 2012 11 03 at 14 52 10

The template and home page are available in the Part 2 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 to make the page available on the web via a Force.com site, including a custom web address.  

 

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 20 October 2012

Press Enter to Submit

As is so often the case with my blog, this post is in response to repeated questions on the same topic on the Developerforce Discussion Boards. This is one that has been cropping up for 3-4 years now, and concerns form behaviour when pressing the enter key in an <apex:inputField/> or <apex:inputText/> component.

The only reference to the expected behaviour can be found in the HTML 2.0, which states:

When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.

Over the years browsers have interpreted the enter key in different ways. Early versions of Firefox required an <input type=’submit’> element in the form for the submission to take place. Internet explorer would submit the form but if there was only a single field it would omit the name/value from the submit button, but if there was more than one field the button information would be included.

Modern browsers now share common behaviour, in that the enter key is interpreted as a request to submit the form via the first submit button. However, if you want to use a different button to submit the form, or you have to contend with old (and unsupported) browsers you need another solution.

In my case, I have a form that is used to search for duplicate accounts before creation. This has a number of text input fields to capture the search criteria and a couple of buttons to clear the criteria or execute the search. A screenshot is show below:

Screen Shot 2012 10 20 at 11 17 25

When the user hits the enter key, I want to carry out the search via the second button rather than the first so the default browser behaviour won't work for me in this case.

The solution is to bind a javascript handler to the onkeypress event of each of the inputs. In my case I've named it 'noenter':

<apex:inputText id="name" value="{!searchCriteria.name}" onkeypress="return noenter(event);"/> 

the noenter function has some code at the end to get at the actual code of the key pressed, taking into account the number of different ways that browsers may present it, and then if the code is 13, aka the enter key, then the search button element is located on the form and its click method executed.  Note that if the enter key is detected, the function returns false - this is a vital part of the solution as it tells the browser not to continue with the default behaviour, which was to submit the form via the first button.  If false isn't returned, you are into an interesting race condition with two form submissions battling it out for supremacy.

    <script>
      function noenter(ev)
      {
         if (window.event)
         {
             ev=window.event;
         }
         
         var keyCode;
         if (ev.keyCode)
         {
            keyCode=ev.keyCode;
         }
         else
         {
            keyCode=ev.charCode;
         }
         
         if (keyCode == 13)
         {
            var ele=document.getElementById('{!$Component.form.crit_block.crit_section.searchbtn}');
            ele.click();
            return false;
         }
         else
         {
            return true;
         }
      }
   </script>

I've tested this on Chrome, Firefox, Opera and Safari and it behaves as expected - if you encounter any problems, please let me know via the comments section below.   

Saturday 13 October 2012

London Salesforce Developers Meetup

The London Force.com Developers group is now on meetup.com and has a new name - London Salesforce Developers. The inaugural meeting of this new look group took place on 10th October 2012 at a new location - the London Offices of 10 Gen.  A further change is that Wes Nolte now has some Salesforce assistance to organise these events, in the shape of Developer Evangelist John Stevenson.  

The subject for the meetup was a "Teardown of what happened at DFX", attempting to bring some of the excitement of Dreamforce 2012 to those who were unfortunately unable to attend, via a panel guided discussion session.  Although I'd volunteered for the panel, I hadn't actually participated in one of these before so wasn't 100% sure what to expect.

This also featured a special guest from Salesforce.com - Adam Seligman, Vice President of Developer Relations for Salesforce.com and Heroku.  If you've seen the Developer Keynote from Dreamforce 2012, Adam was the speaker opening and closing it.  There was an additional, unexpected guest from Salesforce.com - Peter Coffee - which I'm pretty sure is the first time we've had someone with their own wikipedia page at one of these events. The fact that we are seeing some of the senior executives from the US attending these events shows how important these meetups are.

The evening was compered by Wes Nolte and the panel consisted of myself, Andy Mahood, John Stevenson and Adam Seligman - here we are in all our glory:

I now know that a session of this nature consists of the panel arranged in front of the audience, each responding to questions posed by the audience.  Imagine Question Time with and for geeks :)

The first question was put by Wes to get the ball rolling, and asked what inspired us most about DFX.  Predictably there was plenty of reference to the Dev Zone, as this had an incredible amount of attendance, excitement and energy around it.  The questions were then opened up to the audience where, after a slightly slow start the questions came thick and fast.

Another advantage to Salesforce involvement is increased levels of schwag, including the ever popular Force.com baseball caps and copies of Dan Appleman's Advanced Apex Programming book.  Some of the goodies were handed out for the best questions during the Q&A, while the rest were handed to the panel to award for the correct answers to trivia questions.  As many of my readers will know, I've been building an online testing system in Force.com  and had recently released a workflow test, so unsurprisingly my question was on workflow actions.  

That concluded the organised part of the evening, so we then made our way to The Fox to continue the networking and discussions late into the night.

For those that couldn't attend, there's a highlights reel available on youtube courtesy of @Sarah_tquila - make sure you get yourself along to the next one!

 

Saturday 6 October 2012

Snippet View

Something I've had kicking around in my toolbox for a while now is a Snippet View page - this is a Visualforce page that provides headline information on objects that haven't been shared with the user.  This allows, for example, a user to see the name and type of an account and the user that is the owner even though the sharing rules don't give them access to the full account record.  In a private sharing model this can be a useful way to prevent duplicates - the user can see if an account already exists and request access to it without exposing sensitive information.

Back when I originally wrote this I had a set of fields that were retrieved and stored as text values in a custom class for use on the page.  One downside to this was that no specialised formatting associated with the field (if it was a rich text area, for example) applied, as they were simply text values.  With the introduction and subsequent improvements around Field Sets, I've rewritten my snippet view page and controller to deal with actual fields rather than text representations and decided it is a good candidate for a blog post.  

A further improvement is that the snippet page now handles any subject type, standard or custom, as long as there is a field set named 'Snippet' defined (or in fact a field set name 'KAB_TUTORIAL__Snippet', as my dev org has a namespace!).

The controller is pretty straightforward

public class SObjectSnippetController {
	/* The id of the object provided on the URL */
	public Id objId {get; set;}
	
	/* The sobject type, determined from the ID */
	public String sobjType {get; set;}
	
	/* The fields in the field set */
	public List<Schema.FieldSetMember> fsMems {get; set;}
	
	/* The sobject, with only the members of the field set populated */
	public Sobject sobj {get; set;}
	
	/* Error string - used if there is no Snippet field set defined */
	
	public String errorStr {get; set;}
	
	public SObjectSnippetController()
	{
		/* extract the id from the URL */
		objId=ApexPages.currentPage().getParameters().get('id');
		
		/* determine the sobject type and its defined field sets */
		Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
		Map<String, Schema.FieldSet> fsMap = null;
    	for (Schema.SObjectType sot : schemaMap.values())
    	{
     		Schema.DescribeSObjectResult descRes=sot.getDescribe();
     		String kp=descRes.getKeyPrefix();
     		if (kp==((String) objId).substring(0, 3))
     		{
     			sobjType=descRes.getName();
     			
     			/* store the field sets for later processing */
     			fsMap=descRes.fieldSets.getMap();
     			break;
     		}
    	}
    	
    	if (null!=fsMap)
    	{
    		/* locate the field set for the snippet fields */
    		Schema.FieldSet fs=fsMap.get('KAB_TUTORIAL__Snippet');
    		if (null!=fs)
    		{
    			/* use dynamic SOQL to retrieve the fields in the set */
    			fsMems=fs.getFields();
		        String query = 'SELECT ';
		        String fieldsStr='';
        		for(Schema.FieldSetMember f : fsMems)
        		{
            		fieldsStr += ', ' + f.getFieldPath();
        		}
        		
        		query += fieldsStr.substring(2) + ' FROM ' + sobjType + ' LIMIT 1';
        		sobj=Database.query(query);
    		}
    	}
    	
    	/* if we don't have an sobject, that implies the administrator hasn't defined a field set */
    	if (null==sobj)
    	{
    		errorStr='Your administrator has not defined a snippet view for this object type';
    	}
	}
}

The key feature of the controller is the declaration

public class SObjectSnippetController {

As this is implicitly 'without sharing', this allows the record to be retrieved without the sharing rules being applied to the currently logged in user.

The page simply iterates the list of fields or displays the error message:

<apex:page controller="SObjectSnippetController">
  <apex:pageBlock title="Snippet View" mode="maindetail">
    <apex:pageBlockSection >
      <apex:repeat value="{!fsMems}" var="fld" rendered="{!ISBLANK(errorStr)}">
        <apex:outputField value="{!sobj[fld.fieldPath]}" />
      </apex:repeat>
      <apex:outputText value="{!errorStr}" rendered="{!NOT(ISBLANK(errorStr))}" />
    </apex:pageBlockSection>
  </apex:pageBlock>
</apex:page>

 

If I attempt to access a record that isn't shared via the regular UI, I receive the expected insufficient privileges error:

 

If I then access the same record via the SnippetView visual force page, I can see the fields that the administrator has added to the snippet field set:

 

 

However, if I attempt to access an object that doesn't have the field set defined, I receive an error message detailing this:

 

Saturday 29 September 2012

Thoughts on Dreamforce 2012

Dreamforce is Huge!

In fact it was huge in 2011, I really need a new word for 2012.  Over 90,000 registrants and I've heard numbers between 50 and 80,000 actually turned up.  As well as the Moscone Center (that's North, South and West), Dreamforce now includes 34 hotels.  It feels like there's a tipping point approaching - just how much bigger can it and stay in its current location.  We all know Marc Benioff loves San Francisco so I imagine it would be hard for him to move it to somewhere like Las Vegas.  Another idea I heard was to split it by functionality into multiple weeks - personally I think that would lose some of the sense of occasion - a drip feed of small conferences rather than one mega-event.  I'd also imagine that would put quite a lot more stress on the SFDC employees - having to organise and staff multiple events, trying to get prospects, customers and partners to attend each of these in equal measure - that sounds like it would bring as many problems as it would solve.  All I know so far is that the Moscone Center is booked for November 18th-22nd 2013, so it looks like its staying put for a while.

Salesforce Values Its MVPs

If you are an MVP you get treated rather well at Dreamforce - thanks mainly to the the amazing Erica Kuhl.  This year included a tour of One Market, a bowling event, a shoutout at the Community Keynote, Force.com MVP lunch (thanks Dana Le and Nick Tran), VIP seating for the keynotes and much more.  

Get To The Community Lounge

The Community Lounge is a great innovation. Its the place to find an MVP outside of their sessions and there were daily presentations on hot community topics.  Aside from that, the wifi was solid and there were comfortable seats and beanbags.  When I needed to work during the event, or relax and eat lunch, this was my preferred location.

Keynotes Are Popular And Run Long

If you want to hear the keynotes in person, you need to be up with the lark.  The rooms aren't big enough to hold everyone that wants to attend, so you need to get in line early, especially if you want a good view.  I always enjoy Dan Darcy's involvement in these - there's a real frisson of excitement when the screens switch over to him and his team.

Keynotes always overrun, but this year it was pretty much a full hour over.  Something to bear in mind when reserving your spots at sessions immediately after - I had nothing scheduled until 12 but still didn't make it.  Being in the VIP seats a few rows from the front does make up for it though.

Don't Stress About Your Agenda

There are so many sessions on at any one time, you are never going to be able to fit in all of those that you want to attend. Add to that keynotes running long, unexpected networking opportunities, meetings with or entertaining customers, distances between venues being larger than expected and simply being tired, and you'll find you can't even keep up with those that you added to your agenda. Don't sweat it, most sessions aside from roadmap are recorded and made available on the Dreamforce Youtube Channel so you can always catch up afterwards.  Once you realise that you are missing most of the sessions anyway, due to their being only one of you, its easier to relax about it.  Get to as many sessions as you can, but don't get so hung up that you miss out on the rest of the conference.

Dev Zone Is Awesome

Bigger and better than ever this year.  Unconference, mini-hacks, community common, developer theatre, touch stadium and more sessions than you could hope to get to.  The Workbooks and Salesforce Touch Platform books were very popular - by the second day these were being rationed and handed out from a counter rather than the free-for-all of the first day, which presumably depleted stocks to a worrying level. Throw in video games, code consults, t-shirt printing and a mini-expo and what more could you want.

Developer Keynote Is Unmissable

Okay so as a developer I'm biased, but this was the first year with a Developer Keynote, which included exciting announcements, entertaining demos (even, dare I say especially, those that didn't work) and a free copy of Advanced Apex Programming for everyone that attended (including those that were in line but couldn't get in). Next year this needs to be in the big room and last 3 hours like Marc Benioff's keynote!

You Don't Have to Stay in a Hotel

The hotels fill up quickly around Dreamforce time - the amount of people staying at the airport or over in Oakland was surprising.  If there are a group of you going, consider hiring a house or an apartment.  This is our our team from BrightGen did it this year and it works really well, you get the common areas of the kitchen and lounge plus your own space (as long as there are enough bedrooms!).

You May Be Going "Out Out"

When you leave your hotel/apartment/house in the morning, you may think you are just spending the day at the conference and will be returning prior to heading out to one of the parties in the evening.  Be prepared that this may not be the case - there are so many events, drinks, meetups and tweetups going on that you can easily find yourself out for the whole day and evening.  

You're Going To Need A Bigger Suitcase

Pretty much every everything on the baggage carousel at Heathrow was spherical or had a 'heavy' tag on it.  Make sure you leave plenty of room for schwag - even if you don't plan on getting any, you are bound to end up with some books and t-shirts at the very least. Failing that, buy an empty suitcase out there and fill it up for your return.

Nothing On Announcements?

You may be surprised that none of my thoughts on Dreamforce cover the Force.com/developer announcements - that's because the next London Meetup will be a panel guided discussion on these topics, so if you want to hear more about those then join us for that.  If you need another reason to attend, Adam Seligman, VP of Developer Platform Marketing for Salesforce.com, is the special guest. 

Tuesday 18 September 2012

Guest Post - Dreamforce '12 Delivers Real-Life Socialized Service Model

In another first for the Bob Buzzard Blog, it's Guest Post time.  Ashley Furness is a CRM Market Analyst at Software Advice.  A couple of weeks ago I tweeted out a link to a story that Ashley wrote and we thought it would be cool for her to write a version specific to this blog and its audience.  Take it away Ashley ...

Dreamforce '12 Delivers Real-Life Socialized Service Model

Dreamforce - the cloud computing event that draws more than 50,000 to San Francisco every year - kicked off this week with more than 750 sessions for developers, media and other industry power players.

In addition to the planned events, organizers will again feature the "Salesforce Service Cloud Call Center,"  but this year it's all about social. This live customer service response hub will provide onlookers a chance to see expert social response in action. Why social for customer support? 

“I don’t think there’s any company out there that doesn’t need to be thinking about [customer service through social]. I guarantee your customers are already using the channel, and they’re probably already talking about your brand,” says Fergus Griffin, senior vice president of solutions marketing for Salesforce.com.

"Salesforce Service Cloud Call Center" observers should be able to glean some tips by watching, but here's a few best practices you should take home from the event.

Hashtag Common Queries

Griffin told me that last year during Dreamforce most interactions across all service channels involved questions about where things were located, recommendations for events, and tips for getting around the conference. Hashtags allow customer service managers to instantly create a knowledge base for topics such as these.

During this year’s event, Salesforce Service Cloud Call Center agents might hashtag common queries with things like #Dreamforcemaps, #Dreamforcefood or a more general tag like #Dreamforcehelp.

Then, if a Dreamforce visitor tweets, “Where’s the closest restaurant?” the agent could respond “@UserName check out everyone’s food recommendations by searching #Dreamforcefood!” When they search that hashtag, the visitor could scan through everyone’s suggestions. This saves the agent time, while still providing a helpful personal response.

Depending on what hashtags emerge, you're company may also choose to add these to the self-service knowledge base on the website. 

Post Publicly First

John Rote, Vice President of Customer Experience at Bonobos, compared support through social media like troubleshooting in a coffee shop or bar.

“It’s likely more people will hear about it and pull friends from across the room to listen. This can mean a bad experience is shared further, faster,” says Rote, who will sit on a social customer service panel at Dreamforce called “How Small Businesses Keep Up with the Velocity of Customer Service."

To mitigate this risk, Rote suggests companies always acknowledge the comment in social before taking the interaction to another channel. This approach publicly demonstrates that the company is listening and responds to everyone. If the interaction was taken straight to email, no one would know customer service addressed the problem.

This is also useful for companies that stream their Twitter feed in the infrastructure of the website. Again, it adds a personal face to the company and shows website visitors you're listening. 

Prioritize Thoughtfully

One of the biggest challenges with providing customer service through social is dealing with the sheer volume of requests. Griffin said companies should have a well-defined strategy for prioritizing responses.

This should include ranking factors from social–a Klout score, for example–and customer history. A company might choose to respond first to longtime customers or those with a history of high-value purchases.

“Companies should strike a balance between who [the customer] is in the community, but also who they are to you,” Griffin says.

Engage Customers You Might Never Have Known

Customer service through social media is not just about providing another interaction channel in addition to phone, email or live chat. Bonobos found out through customer surveys that social media support engaged customers who might never have sent their question otherwise.

“The thing that was really amazing to us was 3-4 months after [implementing our social customer service strategy], we found out that most of those customers identified never would have reached out to us if they weren’t able to do it over Facebook or Twitter,” Rote says.

So, take a minute to check out the Salesforce Service Cloud Call Center this year and learn other strategies for elevating your social customer service strategy. 

Research for this article was provided by Help Desk Software Advice.

Video: http://www.youtube.com/watch?v=rO5tdE9mtnE

 

 

Sunday 9 September 2012

Online Quizzes Built on Force.com

If you're a Facebook friend of mine, over the last few weeks you'll have seen status updates regarding an online quiz app that I've been working on.  I started off calling it an online testing system, but decided against that as it had too many connotations of an app that allowed unit tests to be executed!

My original idea for the app was to use it to determine individual's readiness for Salesforce certification exams and crowdsource the questions as a free for all. However, I'm rethinking how the questions will be authored based on the various compromises of real questions that have taken place this year.  I'm still planning to allow the community access to author questions, but in a more controlled fashion and limiting the number of questions an individual can write on a single topic.

A screenshot of a question is shown below :


One of the key aspects for me is the ability for candidates to give a percentage rating to their confidence in the answer.  This is based on my approach to taking the real exams, as it allows me to determine how close I think I am to the pass mark.  I find it particularly useful when assessing a candidate's readiness, as if they are getting questions wrong when they were 100% confident in their answer, that implies there is a fundamental lack of understanding in that particular concept.  There's also a couple of free text areas for candidate notes and feedback on each question.

Each question is marked with a topic, which identifies which test it belongs to, and an area which is simply a free text sub-topic - Workflow is an area under the Force.com topic for example. The areas are used to provide feedback to candidates about areas they are weak on - at the moment its simply the first five unique areas encountered when marking.  I do plan to make this more sophisticated, taking into account the candidate's confidence and the number of incorrect answer in each area, for example

Something that applies both to tests and surveys (and many other question and answer scenarios) is the requirement to decouple the questions from those presented in a test instance. For example, if a questions is worded incorrectly and I fix it, I don't want to retrospectively update that question as posed to a particular candidate, as its not the question they answered. The trick here is to have the concept of questions as templates, and clone these when creating a test instance. Mine are called question responses, and that is where things like the feedback live.

Questions can be single answer (radio button), multi-answer (checkboxes), putting answers in order (picklists) or free text (long text area). The first three are automatically marked when the test is submitted, while free text requires a human to review the text.

As I want to be able to have (at least) users and contacts as test candidates, I couldn't use master-detail relationships and standard roll up summary fields. Instead I used this excellent utility from Anthony Victorio that allows roll up summary behaviour via regular lookup fields. It only takes a few minutes to set up and has worked fine for me thus far. The only trick is to remember to "touch" all the child objects when you add a new parent summary field.

The app is built on Force.com sites using composition templates.  The design is from Free CSS Templates - a great resource if, like me, design isn't your strong suit.

At the moment there's just a single test on Dreamforce X to allow me to test the site out in a controlled fashion.  So give it a go and let me know what you think.  If you would be interested in writing or marking questions, please tick the boxes on the test signup form.  On the results page there's a link to tweet out the score - don't feel any pressure to do this, but if you do want to that would be cool.


You can access the site at: http://tests.bobbuzzard.org

Wednesday 5 September 2012

Book Review - Advanced Apex Programming

Disclaimer: Dan Appleman sent me a copy of the book to review.  I was going to buy it anyway and reading it has convinced me that would have been one of my better decisions.




In a first for the Bob Buzzard Blog, its book review time. If you are a Force.com developer you may well have seen a flurry of activity in early August around a new book by Dan Appleman entitled "Advanced Apex Programming". My curiosity was piqued by the news as it was immediately clear to me (and no doubt many others - the wonder of hindsight!) that there was a big gap in the market here. It received the Dave Carroll seal of approval shortly after, which is more than enough justification to purchase as far as I'm concerned.

The first line of the introduction promises that this book is not a rehash of the Salesforce Apex documentation and it was a relief to find that really was the case, even towards the end of the book which in my experience of development books is when authors can run out of steam and resort to regurgitation.

The style of the writing is quite conversational, which makes the text sections an easy read for a book that covers advanced development techniques and patterns. The example code is often a different matter - not because its badly written, but because it takes (or took me anyway!) a couple of iterations to understand exactly what it is doing. Its worth putting the effort in to understand the code though, as when you do get to grips with it there will be a few light bulb moments when you realise how useful this new technique you have learnt would have been in the past.

Probably the most interesting section for me was Application Architecture and Patterns - there are some great concepts for trigger handling and asynchronous processing. I also learnt a few things about Apex - at least one of which made me want to bang my head against a wall and cry out "why?". I'm pretty sure that knowing these facts will save any reader several times the book's cost.

The final chapter of the book covers patterns and approaches for developing managed packages and is definitely worth reading and re-reading if you are in that space. Dealing with the presence (or lack thereof) of Person Accounts, for example, is something I've never really thought about in this context, even though I have worked with a number of organisations that have this feature enabled.

A refreshing change for a book presenting development best practice was the recognition that:

(a) the point of professional software development is to make money, and
(b) developing solutions for individual customers on a consulting basis is very different to developing applications intended for widespread use

and thus your approach to writing code and tests needs to reflect this.

Finally, a few words for the less experienced. If you are relatively new to the Apex programming language, you'll find large parts of this book a struggle. However, there is one chapter that every Apex programmer should be made to read before being let loose on a live organisation and that is chapter 3 - Limits. Limits are something that all designs should take into account, because regardless of how unlikely you feel it is that your code will ever be used in a way that would breach limits, you can't predict what will happen in the future and what other code yours will be forced to co-exist with. This chapter has some excellent examples of how code can fail to scale and what you can do to avoid this.

I learnt a lot from reading this book, more than I'd expected to, if I'm honest, and I'm sure I'll learn more when I use these patterns in anger and re-read chapters. I'd recommend it if you are looking to improve your Apex development skills (and if you think your Apex development skills can't be improved, then you should definitely read it!).

Saturday 1 September 2012

The Other Side of Certification

As regular readers of this blog will know, I'm a big fan of Salesforce certification (in fact, I'll be talking about this in my Dreamforce 12 Community Commons session in September).  Recently my eyes have been opened to the other side of certification.

Over the last few weeks I've been helping out with the beta for the Salesforce Service Cloud certification.  While I've had plenty of experience of these exams from a candidate perspective, this is the first time I've been able to contribute back to the exams, and its been a real eye opener.

What has surprised me most is the amount of time that is involved - I've personally spent a number of days on this, and there are quite a few of people involved.  The effort that goes into ensuring the exam is accurate, worded appropriately and at the correct level is quite frankly staggering and way more than I was expecting at the outset.

As I've mentioned in an earlier blog post, the service cloud exam had to be withdrawn due to reports of questions and answers being publicly available.  While this was irritating for potential candidates, the impact on those involved with producing the exams must be orders of magnitude higher.  It isn't like a new exam can simply be churned out in a semi-automatic fashion by a dedicated team, rather these are created by subject matter experts from Salesforce and the wider community who all have day jobs and have to make time to do this.  Even more reason, if it were needed, not to capture questions and ruin it for everyone. Up to now, I've admonished people requesting questions and answers fairly mildly and tried to encourage the askee to make the effort to learn.  No more - now that I've seen first hand the effort that goes into these, any requests of this nature will be dealt with human torch style - flame on!

So next time you are taking one of the exams and feel like the questions are intentionally trying to confuse or trick you, keep in mind that nothing could be further from the truth.  They've been put together by a group of human beings trying their level best and in many cases doing it for nothing with the best interests of the Salesforce community at heart.  This is also something to bear in mind if you are tempted to capture and share the questions and answers.

If you are going to Dreamforce 12, you'll find the members of the certification team in the campground. If you are a certified professional, why not take the time to visit and maybe tell them how much you appreciate their hard work over what must have been a very difficult year - but for them, you wouldn't have any exams to take in the first place. I'll certainly be doing that - maybe I'll see you there.

Saturday 25 August 2012

Sobject Preview Panel

Something that I find myself doing at fairly regular intervals is providing selectlists on Visualforce pages to allow a user to choose from a list of records.  Sometimes its because I want to restrict the selection due to complex rules, so a lookup isn't suitable, sometimes I'm not using an inputfield to capture the id, but mostly its because I find it a lot faster to select from a drop down list.

The downside to this is that if the record names are quite similar, it can be difficult for users to pick the right one.  When this is the case, I usually provide a dynamically rendered preview of the record in the page so that the user can check it is the correct record.

Here's a simple example of a dropdown - which of the 'Blog Account' entries is the one I am interested in?


Adding a preview pane shows me the detail of the selected item:



as I actually wanted BrightGen, it is immediately apparent that I've chosen the wrong record (I know the name makes that pretty clear too, but this is only an example :).  I can then choose the correct record and verify that from the details:


The apex controller is very simple - it has a method to retrieve selectoptions for 10 accounts and provides a property to store the user's selection:

public with sharing class DynamicDetail
{
	public String chosenAccountId {get; set;}
	public List<SelectOption> getAccounts()
	{
		List<SelectOption> accOptions=new List<SelectOption>();
		accOptions.add(new SelectOption('Choose', 'Choose'));
		for (Account acc : [select id, Name from Account limit 10])
		{
			accOptions.add(new SelectOption(acc.id, acc.Name));
		}
		
		return accOptions;
	}
}


The page is also pretty simple - a pageblock to choose the account and an actionsupport to rerender the preview panel when a selection is made. Note that I've put the preview pane in the stop facet of an actionstatus. This means that when the user chooses a different account, I don't have to worry about clearing down theuser is entering information that will be presented in a particular context.  For example, entering markup into a rich text field that will appear in a branded Force.com site - having a preview of the content inside the branded page gives you an immediate indicator as to how well the content integrates with the site look and feel.



By the way, if you are seeing odd line numbering on the code samples, this appears to be a bug in chrome.  Hopefully it will be fixed soon.

Saturday 11 August 2012

The Importance of Page Messages

This week's blog is inspired by a lengthy thread that I was involved in on the Visualforce Discussion Board.  This concerned an action method tied to an onchange event not being called.  I was able to track down what the problem was thanks to the <apex:pageMessages/> component, which displays all error messages associated with the page.

Here's an example of how this component can help track down problems when rerendering parts of a page.  The following page simply presents a button to invoke an action method that increments the counter:

<apex:page controller="PageMessages">
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Count  = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

The controller contrives to produce an error rather than increment the counter:

public with sharing class PageMessages {
 public Integer countVal {get; set;}
 private Boolean fakeError=true;
 
 public PageMessages()
 {
  countVal=1;
 }
 
 public void click()
 {
  if (fakeError)
  {
   ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Fake Error'));
  }
  else
  {
   countVal++;
  }
 }
}

The page when rendered is as follows:




Opening the page in a browser window: and clicking the 'Click Me' button results in no change to the count value, and as the rerender attribute turns the command into an AJAX request, it looks like clicking the button has no effect whatsoever.  In this case I can turn on debug logging and see that an error message is being added to the page:

14:37:19.021 (21244000)|CODE_UNIT_STARTED|[EXTERNAL]|01p80000000cOqp|PageMessages invoke(click)
14:37:19.021 (21302000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:6
14:37:19.021 (21322000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:524
14:37:19.021 (21338000)|METHOD_ENTRY|[1]|01p80000000cOqp|PageMessages.PageMessages()
14:37:19.021 (21345000)|STATEMENT_EXECUTE|[1]
14:37:19.021 (21357000)|SYSTEM_MODE_ENTER|false
14:37:19.021 (21370000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:5
14:37:19.021 (21376000)|STATEMENT_EXECUTE|[1]
14:37:19.021 (21385000)|SYSTEM_MODE_EXIT|false
14:37:19.021 (21394000)|METHOD_EXIT|[1]|PageMessages
14:37:19.021 (21443000)|SYSTEM_MODE_ENTER|false
14:37:19.021 (21450000)|HEAP_ALLOCATE|[12]|Bytes:5
14:37:19.021 (21456000)|STATEMENT_EXECUTE|[11]
14:37:19.021 (21465000)|STATEMENT_EXECUTE|[13]
14:37:19.021 (21469000)|STATEMENT_EXECUTE|[14]
14:37:19.021 (21612000)|HEAP_ALLOCATE|[14]|Bytes:10
14:37:19.021 (21689000)|SYSTEM_METHOD_ENTRY|[14]|ApexPages.addMessage(ApexPages.Message)
14:37:19.021 (21697000)|ENTERING_MANAGED_PKG|
14:37:19.021 (21728000)|VF_PAGE_MESSAGE|Fake Error
14:37:19.021 (21738000)|SYSTEM_METHOD_EXIT|[14]|ApexPages.addMessage(ApexPages.Message)
14:37:19.021 (21746000)|SYSTEM_MODE_EXIT|false
14:37:19.021 (21775000)|CODE_UNIT_FINISHED|PageMessages invoke(click)

Its a lot easier just to add an <apex:pagemessages/> component to the page and rerender that as well:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Count  = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

which displays the error upon clicking the button:


Another scenario where its pretty much impossible to locate the problem without an  <apex:pageMessages/> component is where there is a required field on the page:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Text1 = " />
      <apex:inputText value="{!text}" required="true"/>
      <br/>
      <apex:outputLabel value="Count = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

This time the controller doesn't fake an error, the counter value is simply updated:

public with sharing class PageMessages {
 public Integer countVal {get; set;}
 public String text {get; set;}
 
 public PageMessages()
 {
  countVal=1;
 }
 
 public void click()
 {
  countVal++;
 }
}

As the required component isn't an <apex:inputField/>, there is no decoration of the element to indicate that it is required.  Clicking the 'Click Me' button once again appears to do nothing.


Checking the log this time reveals nothing that can help.  The click method simply isn't being called:

25.0 APEX_CODE,FINEST;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WORKFLOW,INFO
15:06:55.036 (36399000)|EXECUTION_STARTED
15:06:55.036 (36462000)|CODE_UNIT_STARTED|[EXTERNAL]|06680000000Tmbw|VF: /apex/kab_tutorial__PageMessage1
15:06:55.046 (46438000)|CODE_UNIT_STARTED|[EXTERNAL]|01p80000000cOqp|PageMessages <init>
15:06:55.046 (46470000)|SYSTEM_MODE_ENTER|true
15:06:55.047 (47466000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:8
15:06:55.047 (47490000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:552
15:06:55.047 (47513000)|METHOD_ENTRY|[1]|01p80000000cOqp|PageMessages.PageMessages()
15:06:55.047 (47539000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47659000)|SYSTEM_MODE_ENTER|false
15:06:55.047 (47678000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:5
15:06:55.047 (47694000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47707000)|SYSTEM_MODE_EXIT|false
15:06:55.047 (47720000)|METHOD_EXIT|[1]|PageMessages
15:06:55.047 (47763000)|VARIABLE_SCOPE_BEGIN|[5]|this|KAB_TUTORIAL.PageMessages|true|false
15:06:55.047 (47808000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:12
15:06:55.047 (47845000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:12
15:06:55.047 (47889000)|VARIABLE_ASSIGNMENT|[5]|this|{}|0x549e58c6
15:06:55.047 (47910000)|SYSTEM_MODE_ENTER|false
15:06:55.047 (47921000)|HEAP_ALLOCATE|[2]|Bytes:5
15:06:55.047 (47935000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47944000)|HEAP_ALLOCATE|[2]|Bytes:5
15:06:55.047 (47956000)|STATEMENT_EXECUTE|[2]
15:06:55.047 (47974000)|STATEMENT_EXECUTE|[3]
15:06:55.047 (47991000)|STATEMENT_EXECUTE|[6]
15:06:55.048 (48000000)|STATEMENT_EXECUTE|[7]
15:06:55.048 (48042000)|METHOD_ENTRY|[7]|01p80000000cOqp|KAB_TUTORIAL.PageMessages.__sfdc_countVal(Integer)
15:06:55.048 (48098000)|HEAP_ALLOCATE|[2]|Bytes:12
15:06:55.048 (48122000)|HEAP_ALLOCATE|[2]|Bytes:12
15:06:55.048 (48142000)|VARIABLE_ASSIGNMENT|[-1]|this|{}|0x549e58c6
15:06:55.048 (48155000)|HEAP_ALLOCATE|[2]|Bytes:8
15:06:55.048 (48190000)|VARIABLE_ASSIGNMENT|[-1]|value|1
15:06:55.048 (48205000)|HEAP_ALLOCATE|[2]|Bytes:8
15:06:55.048 (48231000)|VARIABLE_ASSIGNMENT|[2]|this.countVal|1|0x549e58c6
15:06:55.048 (48260000)|METHOD_EXIT|[7]|01p80000000cOqp|KAB_TUTORIAL.PageMessages.__sfdc_countVal(Integer)
15:06:55.048 (48275000)|SYSTEM_MODE_EXIT|false
15:06:55.048 (48292000)|CODE_UNIT_FINISHED|PageMessages <init>
15:06:55.048 (48814000)|VF_SERIALIZE_VIEWSTATE_BEGIN|06680000000Tmbw
15:06:55.050 (50548000)|VF_SERIALIZE_VIEWSTATE_END
15:06:55.440 (54027000)|CUMULATIVE_LIMIT_USAGE
15:06:55.440|LIMIT_USAGE_FOR_NS|KAB_TUTORIAL|
  Number of SOQL queries: 0 out of 100
  Number of query rows: 0 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 0 out of 150
  Number of DML rows: 0 out of 10000
  Number of script statements: 3 out of 200000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 10
  Number of Email Invocations: 0 out of 10
  Number of fields describes: 0 out of 100
  Number of record type describes: 0 out of 100
  Number of child relationships describes: 0 out of 100
  Number of picklist describes: 0 out of 100
  Number of future calls: 0 out of 10

15:06:55.440|CUMULATIVE_LIMIT_USAGE_END

15:06:55.054 (54063000)|CODE_UNIT_FINISHED|VF: /apex/kab_tutorial__PageMessage1
15:06:55.054 (54077000)|EXECUTION_FINISHED

In fact what has happened is the client side validation has failed and the page is simply redrawn. However, at this point its very easy to start doubting your code and rewriting it randomly - especially if its more complex than my simple example.  Simply adding an <apex:pageMessages/> component that is rerendered avoids all this:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Text1 = " />
      <apex:inputText value="{!text}" required="true"/>
      <br/>
      <apex:outputLabel value="Count = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Clicking the button this time shows the validation error.


As an aside,  due to the required element not being an <apex:inputField/>, the error message is not particularly helpful.  For details on how to improve this, take a look at one of my older posts.