https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shAutoloader.js

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: