# Sunday, 22 May 2011
DocuSign's Hackathon on May 15th and 16th, 2011 brought out some serious talent to DocuSign's new office in downtown San Francisco. For 2 days, developers took a shot at the $25K purse in 3 categories; consumer, enterprise, and mobile.

My app, SocialSign, was based on the T-Mobile Fave 5 campaign:
  • Select 5 Facebook friends
  • Sign an unlimited voice/text contract through DocuSign
  • Merchants manage the contract in Salesforce.
I admittedly spent about 75% of the hackathon working with the Facebook app canvas and Salesforce Sites APIs, which probably explains why SocialSign placed as runner-up in the consumer category (other developers got far more creative with the actual Docusign API).

In the end, it was a great opportunity to demonstrate the feasibility of conducting social commerce through Facebook using Salesforce CRM to manage contacts, orders, and contracts. Thank you DocuSign for hosting such a great event!

(Video demo of SocialSign available here and embedded below)

Sunday, 22 May 2011 20:24:38 (Pacific Daylight Time, UTC-07:00)
# Sunday, 27 March 2011
JAWS is "Just A Web Shell" framework developed by the Facebook IT group for running Force.com web applications on iOS (iPhone/iPad) devices.

JAWS is a "hybrid" mobile application framework that provides a native iOS application on the mobile desktop that instantiates a web browser to a Force.com web page.




Prerequisites:
The Force.com-Toolkit-JAWS-for-iOS project on GitHub includes all of the following steps and source code for building a mobile iOS app on Force.com.

1) Start in XCode by creating a new View-based iPad or iPhone application.



2) Define a UIWebView controller with a default URL of https://login.salesforce.com.
Append the retURL parameter to the URL to define which Visualforce page to load upon authentication.


3) Launch the Interface Designer and create a simple toolbar with UIWebView to emulate a basic browser UI.



4) Build and launch the iOS simulator from XCode to view the Salesforce login page loaded within the UIWebView



5) Upon login, the return URL (retURL) Visualforce page is loaded. In this case, using the jQuery Mobile framework to display a simple mobile UI.
 


That's it! The native web shell runs the Visualforce directly on iOS. With some crafty mobile-friendly UI design in HTML5, the end-user may not even detect the app is actually running entirely in the cloud on Force.com!
Sunday, 27 March 2011 10:38:30 (Pacific Standard Time, UTC-08:00)
# Thursday, 24 June 2010

Chatter Developer Challenge / Hackathon 2010 Roundup

The Chatter Developer Challenge sponsored by Salesforce encouraged Developers to create a wide variety of applications that demonstrate the new Salesforce Chatter API.

The challenge culminated in a Hackthon event on June 22nd 2010 at the San Jose Convention Center where prizes were awarded for various applications.

My entry, Chatter Bot, demonstrated the use of Chatter within a Facility Management application that captured physical world events and moved them to the cloud to produce Chatter feed posts.

Chatter Bot is a system comprised of 4 major components:

  • Arduino board with motion and light sensors (C/C++)
  • Proxy Service (Java Processing.org Environment)
  • Salesforce Sites HTTP Listener (Visualforce/Apex)
  • Facility Management App (Force.com database and web forms)
(Source code to all components available at the bottom of this post)

I was elated to learn a few days before the hackathon that Chatter Bot had been selected as a finalist and I was strongly encouraged to attend. So I packed up Chatter Bot to take the 2 hour flight from Portland to San Jose.

It wasn't until I arrived at the airport that it suddenly dawned on me how much Chatter Bot bares a striking resemblance to a poorly assembled explosive device. Apparently the TSA agent handling the X-Ray machine thought so too and I was taken aside for the full bomb sniffing and search routine.

It crossed my mind to add a bit levity to the situation by making some kind of remark, but I quickly assessed that I was probably one misinterpreted comment away from being whisked off in handcuffs to some TSA lockup room. Ironically, I had no problem with security in San Jose coming back. They must be accustomed to these types of devices in Silicon Valley.

Upon arriving in San Jose, I setup Chatter Bot and configured the San Jose Convention Center (SJCC) as a Building Facility (Custom object) to be monitored.

Several assets were created to represent some rooms within the SJCC.

Finally, the Chatter Bot was associated with a particular room (Asset) through an intersection object called AssetSensors that relates a device ID (usually a MAC address) and an Asset.

Within minutes the motion and light sensors were communicating to the cloud via my laptop and reporting on activity in the Hackathon room.

Given the high quality and functionality of fellow competitors apps, such as the very cool Chatter for Android app by Jeff Douglas, and observations from the public voting, I thought Chatter Bot might be a little too "out of the box" to take a prize. It was a genuinely surreal and surprising moment when I learned Chatter Bot received the grand prize.

Thank you Salesforce for hosting such a great event and thank you to the coop-etition for the encouraging exchange of ideas and feedback during the challenge!

Arduino Sensor

/////////////////////////////
//VARS
//the time when the sensor outputs a low impulse
long unsigned int lowIn;

//the amount of milliseconds the sensor has to be low 
//before we assume all motion has stopped
long unsigned int pause = 5000;

boolean lockLow = true;
boolean takeLowTime;  

int LDR_PIN = 2;    // the analog pin for reading the LDR (Light Dependent Resistor)
int PIR_PIN = 3;    // the digital pin connected to the PIR sensor's output
int LED_PIN = 13;

byte LIGHT_ON    = 1;
byte LIGHT_OFF   = 0;
byte previousLightState  = LIGHT_ON;
int lightLastChangeTimestamp = 0;
unsigned int LIGHT_ON_MINIMUM_THRESHOLD = 1015;
unsigned long lastListStateChange = 0; //Used to de-bounce borderline transitions.

// Messages
int SENSOR_MOTION = 1;
int SENSOR_LIGHT  = 2;

/////////////////////////////
//SETUP
void setup(){  
  //PIR initialization
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(PIR_PIN, LOW);
  
  Serial.begin(9600);
  
  InitializeLED();
  InitializeLightSensor();
  InitializeMotionSensor();
}

////////////////////////////
//LOOP
void loop(){
  
  if(digitalRead(PIR_PIN) == HIGH){
    digitalWrite(LED_PIN, HIGH);   //the led visualizes the sensors output pin state
    if(lockLow){
      //makes sure we wait for a transition to LOW before any further output is made:
      lockLow = false;
      writeMeasure(SENSOR_MOTION, HIGH);
      delay(50);
      digitalWrite(LED_PIN, LOW);   //the led visualizes the sensors output pin state
    }
    takeLowTime = true;
  }

  if(digitalRead(PIR_PIN) == LOW){
    digitalWrite(LED_PIN, LOW);  //the led visualizes the sensors output pin state
    if(takeLowTime){
      lowIn = millis();          //save the time of the transition from high to LOW
      takeLowTime = false;       //make sure this is only done at the start of a LOW phase
    }
    
    //if the sensor is low for more than the given pause, 
    //we assume that no more motion is going to happen
    if(!lockLow && millis() - lowIn > pause){
      //makes sure this block of code is only executed again after 
      //a new motion sequence has been detected
      lockLow = true;
      writeMeasure(SENSOR_MOTION, LOW);
      delay(50);
    }
  }
  
  ProcessLightSensor();
}

boolean InitializeLED(){
  Serial.println("INIT: Initializing LED (should see 3 blinks)... ");
  for(int i=0; i < 3; i++){
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);
  }
}

//the time we give the motion sensor to calibrate (10-60 secs according to the datasheet)
int calibrationTime = 10;

boolean InitializeMotionSensor(){
  //give the sensor some time to calibrate
  Serial.print("INIT: Calibrating motion sensor (this takes about ");
  Serial.print(calibrationTime);
  Serial.print(" seconds) ");
  for(int i = 0; i < calibrationTime; i++){
    Serial.print(".");
    delay(1000);
  }
  Serial.println(" done");
  Serial.println("INIT: SENSOR ACTIVE");
  delay(50);
}

boolean InitializeLightSensor(){
  Serial.print("INIT: Initializing light sensor. Light on threashold set to ");
  Serial.println(LIGHT_ON_MINIMUM_THRESHOLD);
  Serial.println("INIT: 20 samples follow...");
  for(int i = 0; i < 20; i++){
    int lightLevelValue = analogRead(LDR_PIN);
    Serial.print("INIT: ");
    Serial.println(lightLevelValue);
  }
}

boolean ProcessLightSensor(){
  byte currentState = previousLightState;
  int lightLevelValue = analogRead(LDR_PIN);  // returns value 0-1023. 0=max light. 1,023 means no light detected.
  
  if(lightLevelValue < LIGHT_ON_MINIMUM_THRESHOLD){
     currentState = LIGHT_ON;
  }
  else{
     currentState = LIGHT_OFF;
  }
  
  if(LightStateHasChanged(currentState) && !LightStateIsBouncing() ){
    previousLightState = currentState; 
    
    if(currentState == LIGHT_ON){
      writeMeasure(SENSOR_LIGHT, HIGH);
    }
    else{
      writeMeasure(SENSOR_LIGHT, LOW);
    }
    
    delay(2000);
    lightLastChangeTimestamp = millis();
    
    return true;
  }
  else{
    return false; 
  }
}

boolean LightStateHasChanged(byte currentState){
   return currentState != previousLightState; 
}

//De-bounce LDR readings in case light switch is being quickly turned on/off
unsigned int MIN_TIME_BETWEEN_LIGHT_CHANGES = 5000;
boolean LightStateIsBouncing(){
   if(millis() - lightLastChangeTimestamp < MIN_TIME_BETWEEN_LIGHT_CHANGES){
      return true; 
   }
   else{
      return false; 
   }
}

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 
char deviceID[ ] = "007DEADBEEF0";
//Format MEASURE|version|DeviceID|Sensor Type|State (on/off)
void writeMeasure(int sensorType, int state){
  Serial.print("MEASURE|v1|");
  
  Serial.print(deviceID);
  Serial.print("|");
  
  if(sensorType == SENSOR_MOTION)
    Serial.print("motion|");
  else if(sensorType == SENSOR_LIGHT)
    Serial.print("light|");
  else
    Serial.print("unknown|");
  
  if(state == HIGH)
    Serial.print("on");
  else if(state == LOW)
    Serial.print("off");
  else
    Serial.print("unknown");
  
  Serial.println("");
}

Chatter Bot Proxy (Processing.org Environment)

import processing.serial.*;

Serial port;
String buffer = "";

void setup()
{
    size(255,255);
    println(Serial.list());
    port = new Serial(this, "COM7", 9600);
}

void draw()
{
  if(port.available() > 0){
    int inByte = port.read();
    print( char(inByte) );
    if(inByte != 10){ //check newline
      buffer = buffer + char(inByte);
    }
    else{
       if(buffer.length() > 1){
          if(IsMeasurement(buffer)){
              postToForce(buffer);
          }
          buffer = "";
          port.clear();
       }
    }
  }
}

boolean IsMeasurement(String message){
  return message.indexOf("MEASURE") > -1;
}

void postToForce(String message){
  String[] results = null;
  try
  {
    URL url= new URL("http://listener-developer-edition.na7.force.com/api/measure?data=" + message);
    URLConnection connection = url.openConnection();

    connection.setRequestProperty("User-Agent",  "Mozilla/5.0 (Processing)" );
    connection.setRequestProperty("Accept",  "text/plain,text/html,application/xhtml+xml,application/xml" );
    connection.setRequestProperty("Accept-Language",  "en-us,en" );
    connection.setRequestProperty("Accept-Charset",  "utf-8" );
    connection.setRequestProperty("Keep-Alive",  "300" );
    connection.setRequestProperty("Connection",  "keep-alive" );
    
    results = loadStrings(connection.getInputStream());  
  }
  catch (Exception e) // MalformedURL, IO
  {
    e.printStackTrace();
  }

  if (results != null)
  {
    for(int i=0; i < results.length; i++){
      println( results[i] );
    }
  }
}

Visualforce Site Chatter Listener

<apex:page controller="measureController" action="{!processRequest}" 
contentType="text/plain; charset=utf-8" showHeader="false" 
standardStylesheets="false" sidebar="false">
{!Result}
</apex:page>

Controller

public with sharing class measureController {
	public void processRequest(){
    	if(Data != null){
    		system.debug('data= ' + Data);
    	}
    	
    	CreateFeedPosts();
    }
    
    private void CreateFeedPosts(){
    	if(AssetDeviceBindings.size() == 0)
    		return;
    	
    	for(AssetSensor__c binding : AssetDeviceBindings){
	    	FeedPost newFeedPost = new FeedPost();
	    	newFeedPost.parentId = binding.Asset__c;
			newFeedPost.Type = 'TextPost';
	        newFeedPost.Body = FeedPostMessage();
	        insert newFeedPost;
    	}
    }
    
    private string FeedPostMessage(){
    	if(AssetDeviceBindings.size() == 0)
    		return '';
    	
    	if(SensorType == 'motion'){
    		if(State == 'on')
    			return 'Motion detected';
    		else
    			return 'Motion stopped';
    	}
    	else if(SensorType == 'light'){
    		return 'Lights turned ' + State;
    	}
    	else
    		return 'Unknown sensor event';
    }
    
    private List<AssetSensor__c> m_assetSensor = null;
    public List<AssetSensor__c> AssetDeviceBindings{
    	get{
    		if(m_assetSensor == null){
    			m_assetSensor = new List<AssetSensor__c>();
    			if(DeviceID != null){
    				m_assetSensor = [select Id, Name, Asset__c, DeviceID__c from AssetSensor__c where DeviceID__c=:DeviceID limit 500];
    			}
    		}
    		return m_assetSensor;
    	}
    }
    
    private integer EXPECTED_MESSAGE_PARTS = 5;
    private integer DATA_MESSAGE_TYPE = 0;
    private integer DATA_VERSION	= 1;
    private integer DATA_DEVICEID	= 2;
    private integer DATA_SENSOR_TYPE= 3;
    private integer DATA_STATE		= 4;
    
    private List<string> m_dataParts = null;
    public List<string> DataParts{
    	get{
    		if(m_dataParts == null && Data != null){
    			m_dataParts = Data.split('\\|');
    		}
    		return m_dataParts;
    	}
    }
    
    public string Version{
    	get{
    		if(Data != null && DataParts.size() >= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_VERSION];
    		}
    		else
    			return null;
    	}
    }
    
    public string DeviceID{
    	get{
    		if(Data != null && DataParts.size() >= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_DEVICEID];
    		}
    		else
    			return null;
    	}
    }
    
    public string SensorType{
    	get{
    		if(Data != null && DataParts.size() >= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_SENSOR_TYPE];
    		}
    		else
    			return null;
    	}
    }
    
    public string State{
    	get{
    		if(Data != null && DataParts.size() >= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_STATE];
    		}
    		else
    			return null;
    	}
    } 
    
    private string m_data = null;
    public string Data{
    	get{
    		if(m_data == null && ApexPages.currentPage().getParameters().get('data') != null){
    			m_data = ApexPages.currentPage().getParameters().get('data');
    		}
    		return m_data;
    	}
    }
    
    private string m_result = '';
    public String Result{
    	get{
    		return 'ok';
    	}
    }
    
    public static testMethod void tests(){
    	Asset testAsset = new Asset();
    	testAsset.Name = 'Test Asset';
    	testAsset.AccountID = [select Id from Account order by CreatedDate desc limit 1].Id;
    	insert testAsset;
    	
    	AssetSensor__c binding = new AssetSensor__c();
    	binding.Name = 'Test Binding';
    	binding.DeviceID__c = '007DEADBEEF9';
    	binding.Asset__c = testAsset.Id;
    	insert binding;
    	
    	measureController controller = new measureController();
    	controller.processRequest();
    	system.assert(controller.Data == null);
    	system.assert(controller.DataParts == null);
    	system.assert(controller.Version == null);
    	system.assert(controller.DeviceID == null);
    	system.assert(controller.SensorType == null);
    	system.assert(controller.State == null);
    	
    	string TEST_MEASURE = 'MEASURE|v1|007DEADBEEF9|motion|on';
    	ApexPages.currentPage().getParameters().put('data', TEST_MEASURE);
    	controller = new measureController();
    	controller.processRequest();
    	system.assert(controller.Data == TEST_MEASURE);
    	system.assert(controller.DataParts != null);
    	system.assert(controller.DataParts.size() == 5);
    	system.assert(controller.Version == 'v1');
    	system.assert(controller.DeviceID == '007DEADBEEF9');
    	system.assert(controller.SensorType == 'motion');
    	system.assert(controller.State == 'on');
    	
    	system.assert(controller.AssetDeviceBindings != null);
    	system.assert(controller.AssetDeviceBindings.size() == 1);
    	system.assertEquals('007DEADBEEF9', controller.AssetDeviceBindings[0].DeviceID__c);
    	system.assertEquals(testAsset.Id, controller.AssetDeviceBindings[0].Asset__c);
    	
    	system.assert(controller.Result == 'ok');
    }
}
Thursday, 24 June 2010 18:03:10 (Pacific Daylight Time, UTC-07:00)
# Tuesday, 16 February 2010

I spent a few hours tackling this problem, so hopefully this blog post will spare someone else the difficulty of using Salesforce Spring '10 features before it is broadly released.

Spring '10, aka version 18, is a big release for Salesforce Developers. The current version of Eclipse Force IDE defaults to using the version 16 API.

Once Spring '10 was installed on my org (NA1) I committed to using the v18 features on a couple new projects. Unfortunately the Spring '10 release hit a snag, so now the release is being staggered over 30-45 days and the new Force IDE release has been put on hold.

Fortunately, there are a couple workarounds to using the v18 API and features today.

Option 1)
a) Create an Apex class as you normally would in Eclipse and accept version 16 as the default.
b) Eclipse will create a second file next to the class with a 'meta.xml' extension.
c) Edit the -meta.xml file by changing the version to 18 then save.

Option 2)
a) Create an Apex class through the Salesforce browser interface but do not accept the default version 18. Instead, select version 16.
b) Open the Salesforce project in Eclipse and confirm the Apex class is available in the IDE (the current IDE apparently won't automatically sync with classes > v16. At least that was my experience).
c) Back in the Salesforce browser, change the version from 16 to 18.

Finally, depending on which approach used, you'll need to synchronize the Eclipse IDE with Salesforce servers using either the "Deploy to" or "Refresh from" server options by right clicking on the class and selecting from the Force.com option. (Option 1 requires "Deploy to". Option 2 "Refresh from").

Hope this helps!

Tuesday, 16 February 2010 18:28:19 (Pacific Standard Time, UTC-08:00)
# Saturday, 09 January 2010
With the economy being what it is, I can think of no better resolution for a small business owner/entrepreneur than to focus on creating jobs.
Ideally these jobs will be created in the Portland, OR metro area, but we're accustomed to operating with distributed teams and are happy to continue doing so.

Web Application Design / Development
With our shift away from .NET and onto the Force.com platform, a person in this role will need to be competent with:
  • Salesforce API and custom/native objects
  • Apex Controllers and Classes / Object modeling
  • Visualforce Pages / Eclipse
  • Javascript / JQuery / AJAX
Possible job creation triggers:
We're actively involved in re-architecting the i-Dialogue platform to run on Force using the above technologies. This product will be launched on the AppExchange with a Freemium business model. As we start to see adoption, the long-term strategy of the product will be to develop and offer add-on premium application modules.
Demand for custom module development, such as custom shopping carts or product configurators, will also spark growth.

Professional Services Developer / Project Manager
The Pro Service Developer is competent with what's available "out of the box" from Salesforce and our core products, and offers last mile customization and development services in direct response to customer and project requirements.

Responsibilities include:
  • Custom Visualforce template design and branding
  • Custom layout
  • Configuration and implementation services
  • Weekly progress meetings. Customer facing support and requirements gathering.
Desired skills:
  • JQuery UI / Themeroller / Custom Themes
  • HTML / Javascript
  • Visualforce (UI emphasis - no custom controller development)
Possible job creation triggers:
  • Clients and customers requiring custom branding of Salesforce Sites and Portal templates.
  • Custom website, landing page, and portal development on Force.com
  • Multi-channel campaigns executed across email and landing page sites
  • Integration projects involving Sites, Customer/Partner Portal, and Ideas

Account / Sales Manager
The AppExchange coupled with our concept of a Site Gallery will automate much of the Sales and Marketing process, however an Account/Sales Manager will be required to assist current and prospective clients assemble the right mix of software and services for their solution and provide quotes.

Possible job creation triggers:
Once the free version is released, we'll shift to offering premium add-on modules within 2 months.


Risks in 2010
The freemium model is somewhat new to us. We're giving away significant value and functionality in the free version, so it's very likely that only 5-20% of all customers will require our premium services, which in turn will enable new job growth. However, this model leverages the scalability and distribution of the cloud and requires no additional effort on our part to provision new computing resources.

The Web Application Design and Development position requires a Software Engineering approach to using Javascript. This is a common skill found in Silicon Valley, but not so much in Portland, OR. It may be difficult to find just the right person(s) for this role.

Most support functions are handled by Pro Service and Account Managers today, but there may be a need for a specific support role in the future.


Conclusion

The actual number of jobs in each position may vary, but these are the 3 primary job functions we'll seek to create in 2010. The products and features we have planned in 2010 are embracing the cloud in ways unimaginable a couple years ago and I'm very excited to wake up each day in pursuit of these solutions. For me, software development has always been about the journey, and surrounding myself with creative, innovative, and passionate individuals on this journey is important.

If past success is any indicator of the future, then I think our new direction will be successful. Of the 60,000+ customers on Salesforce, many are always seeking to gain more value from their CRM investment by deploying Sites and Portals. By running natively and offering services directly on the Force.com platform, rather than at arms length in a hybrid configuration, we're now able to offer much richer applications and solutions.

If you know of anyone in the Portland, OR area that has these particular skill sets, please have them contact me at mike@cubiccompass.com.

Saturday, 09 January 2010 14:11:55 (Pacific Standard Time, UTC-08:00)
# Sunday, 13 December 2009
Update Jan 2012: This article is way out of date. I've since moved to a MacPro for development, using VMWare Fusion to run some Windows apps.

What would be your ultimate machine for developing applications in the cloud?

I've been mulling over this question for a few weeks and finally got around to putting a solution together over a weekend. There are both hardware and software components:

Hardware
  • Something in between a netbook and laptop, around 4 pounds.
  • 8+ hour battery life
  • 1" thin (Easy to tote)
  • SSD (Solid State Disk 256 GB+)
  • 4 GB RAM
  • 2 GHz CPU+
  • ~$700
I settled on a new Acer 4810 Timeline which met most of my requirements. The exceptions being the Acer has a 1.3 GHz CPU and doesn't have SSD. I wanted the SSD primarily for a speedy boot time, but some tuning of the Win7 sleep options along with the Acer's 8+ hour battery life means the laptop is rarely ever turned off and can quickly recover from sleep mode.

Software

Next up was the software required to fully embrace the cloud. My list of essential cloud tools is no where near as prolific as Scott Hanselman's tool list. But hey, this is a nascent craft and we're just getting started. These tools are essential, in my opinion, for doing development on the leading cloud platforms.

Windows 7
Whoa! I can already hear my Mac toting friends clicking away from this blog post. "WTF? Why Windows 7 for a cloud development machine?" Well, there are several reasons:
  • First of all, even if you do develop on a Mac you likely have a Windows VM or dual boot configured for Windows anyway.
  • Windows has been running on x86 architecture for years. Mac only made the jump a few years ago and is still playing catch up on peripheral device and driver support.
  • Even Google, a huge cloud player, consistently will develop, test, and release all versions of Chrome and Google App Engine tools on Windows before any other platform. Windows developers typically get access to these tools months in advance of Mac users.
  • Eclipse is another tool that is well supported on Windows, above all other platforms.
  • Silverlight support. This is my RIA environment of choice going forward.

Visual Studio 2010 Beta 2
By the time you read this blog, perhaps this version of Visual Studio will be outdated, but it is the first release of VS designed with complete support for Azure.

Windows Azure
You'll need an Azure account to upload your application to the cloud for hosting.

Azure SDK
Great article here for getting started on installing/configuring your machine for the latest Azure bits

Azure Storage Explorer
Azure Storage Explorer is a useful GUI tool for inspecting and altering the data in your Azure cloud storage projects including the logs of your cloud-hosted applications.

Eclipse 
Eclipse is a Java-based IDE that requires the Java runtime

Rather than downloading the latest and greatest version of Eclipse, I recommend downloading whatever version is currently supported by the next essential cloud development tool (below)...

Force IDE
Eclipse Plug-in that supports development and management of Apex/Visualforce code on Salesforce.com's platform (aka Force.com)

Google App Engine
Currently, there are both Python and Java development environments for GAE. Like Azure, GAE supports development and debugging on localhost before uploading to the cloud, so the installation package provides a nice local cloud emulation environment.

SVN
I have a need for both the Windows Explorer Tortoise shell plug-in and the Eclipse plug-in. You may need only one or the other. All the Force.com open source projects are accessible via SVN.

Git on Windows (msysgit)
It seems gitHub is becoming the defacto standard for managing the source code for many web frameworks and projects. Excellent article here on how to install and configure Git on Windows

Amazon Web Services (AWS) Elastic Fox
Nice Firefox plug-in for managing EC2 and S3 instances on AWS. I mostly just use it for setting up temporary RDP whitelist rules on EC2 instances as I connect remotely from untrusted IP addresses (like airports/hotels/conferences).

I highly recommend using the browser based AWS console for all other provisioning and instance management. There's also an AWS management app for Android users called decaf.

Browsers
If you're doing real world web development, then you already know the drill. Download and install Internet Explorer 8, Firefox, and Chrome at minimum. In addition to the Elastic Fox plug-in (above) you'll want to install Firebug. IE 8 now has Firebug-like functionality built-in, called the Developer Toolbar. Just hit F12 to access it (see comparison with Firebug here).

I personally use JQuery for all web development that requires DOM manipulation to handle cross-browser incompatibilities and anomalies.

There are 6-10 other utilities and tools I installed on this machine that aren't specific to the cloud. Installing everything on this list took about 90 minutes (plus a couple hours to pull down all my SVN project folders for Passage and other related projects I manage).

Given that cloud development is all about distributing resources over servers and clients, I like to take a minimalist approach and reduce the surface area and drag of my local environment as much as possible. This improves OS and IDE boot time as well as eliminates a lot of common debugging issues as a result of version incompatibilities.

What about you? What hardware/tools/applications are essential to your cloud development projects?

Sunday, 13 December 2009 12:01:04 (Pacific Standard Time, UTC-08:00)
# Monday, 23 November 2009

I just returned from the 2009 Dreamforce conference last week. Prior to the conference, a few of us on Twitter were bouncing ideas around on how to track all the after hours events and arrange for meeting people while at the conference. Additionally, I was looking for something mobile-browser friendly since carrying a netbook around wasn't going to be practical.

I built the site http://www.dfbuzz.com using Force.com Sites as an excuse to try out a new UI library and address these requirements.

In the end, Twitter ended up being the best way to get realtime information on the conference 'buzz', but I did get some feedback from people that they discovered an event on DF Buzz, or that a comment left on an event was useful in making a decision on whether or not to attend.

Thanks to all the sponsors who posted their events. I have a laundry list of enhancements to make next year. :-)

Monday, 23 November 2009 14:22:00 (Pacific Standard Time, UTC-08:00)
# Saturday, 07 November 2009
Update 2/22/2010 See this article for a better way of using Ajax with Visualforce/Apex.

JQuery and JSON have become my tools of choice for designing and developing Single Page Applications (or SPA). Why?
  1. The user experience is better if the entire page is not refreshed when executing a single action
  2. There's a vast library of JQuery plug-ins and a Developer community to tap into.
  3. Using JSON as the standard object model and separating the UI from the database allows me to design a UI only once and port the application to other cloud platforms (for example, the VF code below can run on i-Dialogue, Google Apps Engine, or Azure by only changing a few lines of code).

Credit goes to Ron Hess for showing me this pattern.I've made very little changes to his Apex code example, except to add JQuery and handling actionFunction rerender callbacks slightly differently.

The basic pattern I'm using for developing Single Page Applications for Visualforce is to call actionFunctions from JQuery, which in turn call Apex controller methods that construct JSON strings. The web page then handles the JSON formatted response in re-rendered outputPanels. The code below demonstrates a simple auto complete function that searches Salesforce Accounts that match a user entered input on each keypress. Clicking on an Account drills down to Account details by calling another actionFunction that retrieves specific Account information.

Some Caveats

  • The outputPanel scripts will execute the first time the page is loaded, so a check for null is required in the callback to prevent first-time execution
  • The dynamic re-rendering of script within an outputPanel makes it difficult to do true functional programming and create closures that elegantly handle the callbacks. More complex applications may have to utilize global state variables, increasing the mutability of the application (and potential for bugs)
  • The mutability of Apex controller properties requires a one-to-one relationship between actionFunction handlers and their corresponding response strings.
  • actionFunctions send the page ViewState in the AJAX XmlHttpRequest and return a blob of XML (apparently using the Sarissa open source library) in the response, which has a slightly slower performance than what you'd get using JQuery's native AJAX utility methods.

Source Code

  1. <apex:page sidebar="false" showHeader="false" standardStylesheets="false" title="AJAX Development Harness" controller="exampleCon">
  2. <style>
  3. body {font-family: Verdana;}
  4. .apexTable { width: 600px;}
  5. .evenTableRow {background-color: #eee;}
  6. .defaultHidden {display: none;}
  7. style>
  8. <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js">script>
  9.     <h3>Using JQuery and JSON with Visualforceh3>
  10.     <apex:form >
  11.         <apex:actionFunction name="apex_searchAccounts" action="{!SearchAccounts}" immediate="true" rerender="searchResultsPanel">
  12.             <apex:param name="searchTerm" value="" />
  13.         apex:actionFunction>
  14.         <apex:actionFunction name="apex_getAccountDetails" action="{!GetAccountDetails}" immediate="true" rerender="accountDetailsPanel">
  15.             <apex:param name="accountid" value="" />
  16.         apex:actionFunction>
  17.     apex:form>
  18.    
  19.     Enter Account Name: <input type=text id='accountInput' /> (type the letters 'uni' to test)<br/>
  20.     <div id="accountsListView" class="view">div>
  21.     <div id="accountDetailsView" class="view defaultHidden">div>
  22.                  
  23.     <script type="text/javascript">
  24.         $(function() {
  25.             $("#accountInput")
  26.                 .focus()
  27.                 .keyup( function (e) {
  28.                     var searchTerm = $("#accountInput").val();
  29.                     if( searchTerm != ''){
  30.                         apex_searchAccounts(searchTerm);
  31.                     }
  32.             });
  33.              
  34.             $(".accountDetailsLink").live('click', function() {
  35.                 apex_getAccountDetails( $(this).attr('id') );
  36.             });
  37.         });
  38.        
  39.         function clearAllViews(){
  40.             $(".view").html('');           
  41.         }
  42.        
  43.         function renderAccountsListView(accounts){         
  44.             if(accounts.length === 0){
  45.                 $("#accountsListView").html("no accounts matching that query").fadeIn();
  46.                 return;
  47.             }
  48.             var tableHTML = '';
  49.             for(var i=0; i < accounts.length; i++){
  50.                 tableHTML += '
  51. ';
  52.             }
  53.             tableHTML += '
  54. + accounts[i].id + '" href="#" class="accountDetailsLink">' + accounts[i].name + '
    '
    ;
  55.             $("#accountsListView").html(tableHTML).fadeIn();
  56.             $("table tr:nth-child(even)").addClass("evenTableRow");
  57.         }
  58.        
  59.         function renderAccountDetailView(account){         
  60.             var tableHTML = '

    Account Details for ' + account.name + '

    '
    ;
  61.             tableHTML += '';         
  62.             tableHTML += '
  63. ';
  64.             tableHTML += '
  65. ';
  66.             tableHTML += '
  67. ';
  68.             tableHTML += '
  69. ';           
  70.             tableHTML += '
  71. Name' + account.name + '
    Type' + account.type + '
    Description' + account.description + '
    Website+ account.website + '" target=_blank>' + account.website + '
    '
    ;           
  72.             $("#accountDetailsView").hide().html(tableHTML).slideDown();
  73.             $("table tr:nth-child(even)").addClass("evenTableRow");
  74.         }
  75.     script>
  76.     <apex:outputPanel id="searchResultsPanel" layout="block" rendered="true">
  77.         <script>
  78.         function apex_searchAccounts_callback(jsonResponse){           
  79.             if(jsonResponse === null || jsonResponse === ''){
  80.                 return;
  81.             }
  82.            
  83.             var accounts;
  84.             eval('accounts = ' + jsonResponse);
  85.             if(accounts !== null){
  86.                 renderAccountsListView(accounts);
  87.             }
  88.         }
  89.         clearAllViews();
  90.         apex_searchAccounts_callback('{!JSONSearchAccounts}');
  91.         script>
  92.     apex:outputPanel>    
  93.    
  94.     <apex:outputPanel id="accountDetailsPanel" layout="block" rendered="true">
  95.         <script>       
  96.         function apex_getAccountDetails_callback(jsonResponse){        
  97.             if(jsonResponse === null || jsonResponse === ''){
  98.                 return;
  99.             }
  100.            
  101.             var account;
  102.             eval('account = ' + jsonResponse);
  103.             if(account !== null){
  104.                 renderAccountDetailView(account);
  105.             }
  106.         }
  107.         apex_getAccountDetails_callback('{!JSONAccountDetails}');
  108.         script>
  109.     apex:outputPanel>    
  110. apex:page>
  111.  
  112. //Controller
  113.  
  114. public class exampleCon {                  
  115.     public String JSONSearchAccounts{ get; set; }
  116.    
  117.     public PageReference SearchAccounts() {
  118.         String searchTerm = Apexpages.currentPage().getParameters().get('searchTerm');         
  119.         String soql = 'SELECT Id, Name FROM Account WHERE Name like \'' + searchTerm + '%\'';              
  120.         List<Account> accountList = Database.query(soql);          
  121.        
  122.         string json = '[';
  123.         for(Account acct : accountList){
  124.             json += '{"id": "' + acct.Id + '", ' +
  125.             '"name": "' + acct.Name + '"},';
  126.         }
  127.         json += ']';
  128.         json = json.replace('},]', '}]');
  129.        
  130.         JSONSearchAccounts = json.replace('\'', '');
  131.         return null;
  132.     }
  133.    
  134.     public String JSONAccountDetails{ get; set;}
  135.        
  136.     public PageReference GetAccountDetails() {
  137.         String accountid = Apexpages.currentPage().getParameters().get('accountid');           
  138.         String soql = 'SELECT Id, Name, Type, AccountNumber, Description, Website FROM Account WHERE Id=\'' + accountid + '\'';            
  139.         List<Account> accountList = Database.query(soql);
  140.        
  141.         if(accountList.size() != 1){
  142.             JSONAccountDetails = '{"error": "Expected only 1 account"}';
  143.             return null;
  144.         }          
  145.        
  146.         string json = '{';     
  147.         json += '"id": "' + accountList[0].Id + '", ';     
  148.         json += '"name": "' + accountList[0].Name + '", ';
  149.         json += '"type": "' + accountList[0].Type + '", ';
  150.         json += '"accountNumber": "' + accountList[0].AccountNumber + '", ';
  151.         json += '"description": "' + accountList[0].Description + '", ';
  152.         json += '"website": "' + accountList[0].Website + '" ';
  153.         json += '}';       
  154.        
  155.         JSONAccountDetails = json.replace('\'', '');
  156.         return null;
  157.     }
  158. }
Saturday, 07 November 2009 13:55:33 (Pacific Standard Time, UTC-08:00)
# Wednesday, 04 November 2009

I'm hooked on Single Page Applications (or SPA for short). If you work in Google Apps everyday, then you know what I mean. Open up GMail, Calendar, or even a Google map, and there's just a single page load. All subsequent interactions occur asynchronously via AJAX, producing a fluid, responsive user interface.

Contrast this with a traditional web application where you fetch a page and are given a master list of records. Click on a record and a completely new page loads containing record details (aka Master-Detail pattern).

Visualforce and Dialogue Script are inherited from a paradigm that mostly encouraged multi-page apps. I've spent the greater part of 2009 re-writing just about every single DScript app to be a SPA, and now that I'm developing applications for Force.com I'm going through the same exercise.

First, don't make the same mistake I did and assume that an AJAX application using Visualforce is as simple as using the AJAX Toolkit. It'll work as a VF page within Salesforce, but can't be published to Sites for security reasons. I found this fact most unfortunate since the Salesforce AJAX API is one of the best AJAX frameworks I've worked with as it encourages functional programming (FP) and embraces all the good parts about Javascript (yes, I am trying to shed my 15 years of Object-Oriented programming and learn FP... a blog entry for another day).

A SPA treatment can be given to a Visualforce page through the use of the <apex:actionFunction /> and <apex:outputPanel /> controls, which I'll cover in detail in Part 2.

The swim lanes diagram below (click here for the full version) demonstrates the lifecyle of a Visualforce SPA page.

1) The browser requests the web page which is rendered with default data.
2) Subsequent page clicks call Apex controller methods via actionFunction.
3) The controller methods construct JSON formatted strings. 3) When the request is complete, actionFunctions re-render outputPanels that contain script templates for processing the returned JSON arrays.

Wednesday, 04 November 2009 21:42:09 (Pacific Standard Time, UTC-08:00)