I’ve written a number of survey applications in the past, some aimed at desktop devices and some aimed at mobile. The common theme for all of these was a round-trip to the server to navigate to the next or previous question, which resulted in a number of abandoned surveys started by people with a less than ideal internet connection. To improve this, I created a Single Page Application, which delivers all of the pages required by the application to the device in a single response. The application retrieves and updates data from the server via API calls rather than carrying out potentially expensive HTTP round trips to the server. For more details on the architecture and pros and cons of Single / Multi Page applications, see Architecting Performant HTML5 Mobile Applications on Force.com: Part 3.
The data model for the survey application is as follows:
The left hand side of the data model is the template survey information – a Survey contains some introductory text and a number of Survey Questions. This is separated from the response data, as I want to be able to change the questions in a Survey without affecting any Surveys that have already been completed, as that could invalidate answers.
When a Survey is sent to a contact, the Survey and associated questions are cloned into a Response that is identified by a unique, non-predictable code, to stop anyone manufacturing codes and answering surveys that they haven’t been sent.
To style my application, I’m using jQuery Mobile (JQM), mainly because I’ve been using it for over three year now and I’m very familiar with it.
JQM is a user interface framework that provides a mobile look and feel for HTML5 web applications. Its purely concerned with presentation, so doesn’t manage your data or provide a business logic layer. Its touch optimised, which means its designed primarily for touch screen interaction. It supports a wide range of devices ranging from phones and tablets to e-readers and desktops – the latter is particularly useful when developing an application, as it allows you to build and test on your desktop or laptop, with access to better tools to debug and inspect the application than you would have on a mobile device.
JQM uses progressive enhancement to enhance HTML content, providing the best user experience possible for a given device. Progressive enhancement takes a layered approach to rendering the page:
- The first layer is regular HTML, which won’t look particularly great but will be functional. Older devices and browsers are still able to access the content, but it won’t be as slick as it would be on a modern smartphone.
- The second layer is CSS, which enhances the view.
JQM determines the enhancements to be applied via HTML5 custom data attributes, so defining a div with a data-role of “header”, for example:
enhances the div to style it as an application header:
These are not standard HTML attributes, instead they are a way to store additional information on the element which has meaning to the application. In this case, JQM knows that when it encounters the data-role attribute, it needs to apply styling to enhance the element. If JQM was removed from the application, this attribute would be ignored.
A physical Visualforce page in a JQM application can contain one or more logical web pages, which allows a Single Page Application to be created. JQM naturally lends itself to Single Page Applications when used with Visualforce, as it hijacks page navigation and form submission, resulting in competition rather than cooperation.
Each JQM logical web page is defined as a div with a data-role attribute of page and these are stacked in the physical Visualforce page:
In this snippet there are two divs with the data-role of page, which equates to two web pages. When the application is opened, the web page contained in the first div is displayed, while clicking on the link swaps the content of the first logical web page out of the DOM and swaps the content of the second logical web page in.
The survey application has the following logical pages:
- Loading page, which is displayed while the survey information is being retrieved from the server
- Start page, which displays basic information about the survey and a start button to allow the contact to launch the survey
- Question page, which displays the question, inputs and any buttons required. This page is updated as the conact navigates through the survey.
- Complete page, which is displayed when the contact has completed all questions and the results have been sent back to the server
- An error page, in case anything goes wrong
- Retrieving the survey record from the server and checking it is not already complete
- Redrawing the question page based on the attributes of the current question
- Sending the contact’s answers back to the server once completed
Knockout is a Model-View-ViewModel framework that reduces the need to write code to manipulate the DOM by allowing you to declaratively bind data to an HTML element. The user interface can then be automatically updated when the value changes, rather than having to locate the element in the DOM and push the new value into it.
DOM elements can bind to the ViewModel properties using custom data-bind attributes:
Activating Knockout is achieved by creating an instance of the ViewModel and passing this as a parameter to the applyBindings method:
When the applyBindings method is executed, the contents of the <span> elements with data-bind attributes are populated with the contents of the referenced property from the ViewModel:
The bindings demonstrated above are static, where the power of Knockout becomes apparent is through Observables – these are two-way, dynamic bindings that notify subscribers about changes. Binding to an Observable means that if the value of the property is changed in the ViewModel, the DOM will automatically update to reflect the change, and user input is automatically captured in the ViewModel property.
In my application, I have a ViewModel Observable named ‘current_question’, which contains the metadata about the question to present to the contact and their response. The following markup conditionally renders a textarea form element based on the type of the ‘current_question’ Observable, and binds the response for the question to the textarea, so that anything the contact enters into the textarea will automatically be written to the response.
If you’d like to learn more about Knockout, there’s an excellent set of interactive tutorials.
Loading the Survey
This is invoked from the application via a function in the ViewModel
The results are sent to the supplied callback parameter – self.responseCB in this case. This creates a SurveyResponse instance in the ViewModel, a collection of Observable SurveyQuestionResponses and sets the value of the current_question property to the first question in the collection:
Saving the Survey
The controller method receives a collection of the Survey Question Response records – as the user cannot alter the Survey Response record itself, this does not need to be sent to the controller. Each Survey Question Response record contains the ID of the parent Survey Response, which is used to mark the Survey Response as complete:
and the response processed by the supplied callback – self.saveCB:
Will They Ever Get Along?
Combining JQM and Knockout worked well for the Landing and Start pages, but the Question page threw up a whole new challenge around the form inputs, as JQM carries out its progressive enhancement once, after which Knockout updates the DOM, conditionally showing and hiding elements as it needs to. This meant that the newly updated and dispayed inputs on the page were styled as default HTML, which was a jarring user experience. Checkboxes, for example, would switch from the JQM styling:
to the default HTML styling:
After a considerable amount of trial and error and digging around on message boards and forums, I was starting to worry that what I was trying to achieve wouldn’t be possible with JQM and Knockout, I finally hit upon a solution – the Knockout appyBindings method can be called multiple times, to allow new markup to be dynamically injected into the page and still bind to the ViewModel. I could therefore replace the input section completely, attach this to the ViewModel via the applyBindings method and then trigger the create event on the input section, which would cause JQM to enhance the new markup.
I therefore had functions in the ViewModel that were executed when the current_question property changed, and replaced the input section with the appropriate form elements for the question, a text area for example:
The full codebase for this application is available in GitHub.
About the Author
Keir Bowden (aka Bob Buzzard) twitter.com/bob_buzzard, is a four-time Force.com MVP and CTO of BrightGen, a Platinum Cloud Alliance partner in the United Kingdom. He holds all 8 Salesforce certifications and is a regular blogger on Apex, Visualforce, and Salesforce1 solutions at The Bob Buzzard Blog, and author of the Visualforce Development Cookbook.