There are a class of form designs where the form fill-in experience follows a basic pattern with a small number of variations. A survey form is the prime example. The variable pieces are the question text and a set of response types. A user who wants to create one of these forms should not have to learn a complicated form design tool. Given the relatively small number of properties that need to be specified to make a survey work, it should be possible to design a single dynamic form that can be shaped by any survey definition — i.e. one form that can render all variations of a survey.
We can design that survey form, but then we need to figure out an easy way for the author to define their survey. This is really just another data capture problem — one that can be handled by a form. So we use a form to design our survey. A form to design a form. Kind of like a play within a play.
To accomplish the form-within-a-form, there are two sets of data. The survey-designer-form captures information for question text and response types and saves this information as its form data. The fill-in-form has built-in variability (dynamic subforms) whose appearance and behaviour are customized from the data produced by the designer form. The design data moves from being form data for the designer form to become metadata for the fill-in form. When the fill-in version of the form runs, it produces its own data — the survey results.
Two Forms in One
Ideally, design and fill would be two separate forms. But two separate forms means moving data between forms. And even a relatively simple operation such as moving data is probably more than we can expect from our target users’ skill set. As well, any multi-step process gets in the way of quickly previewing a survey. To keep the experience as simple as possible, I’ve taken the approach of combining the design and fill-in experience into the same PDF. The advantage is that the user deals with only one form and doesn’t have to manage their data. There are likely better ways to do this. If the experience were tethered to a server (LiveCycle for example :), it would be easier to manage the data on behalf of the user and keep the forms separate. That would also make it easier to use a cool flash UI for the survey-design piece.
But for now, to make the sample easy to distribute, I’ve combined them into one PDF.
Here’s the sample in several stages of processing:
An XFA form can fairly easily house two separate design experiences. In my example, I had two optional top-level subforms: DesignSurvey and FillSurvey. During survey design, the DesignSurvey subform exists. During preview, DesignSurvey and FillSurvey both exist. During fill-in, only the FillSurvey subform exists. Which subform(s) appear is controlled by the form data and by script logic.
The Design mode allows you to create sections and question within sections. The data to define a simple one-question survey looks like this:
<SurveyTitle>A survey about surveys</SurveyTitle>
<QuestionText>Are you married?</QuestionText>
When designing the form, this data resides in the normal place for form data under:
When we switch to "fill-mode", we move the form definition (<DefineSurvey>) to a separate dataset and the fill-in data then lives under <xfa:data>:
<QuestionText>Are you married?</QuestionText>
Once the form is in "fill mode", the PDF can be distributed to users. Enable usage rights so they can save the results. Or better yet, host your survey on acrobat.com.
The form design could expose more options. e.g. conditional logic, more response types, more constraints on responses, styling options. It’s all just SMOP (small matter of programming).
I added a submit button to the form in order to ret
urn the survey results. There are a couple of things that are interesting about the handling of the submission. The survey definition includes a target email address. The submit button gets updated with target and subject with this bit of code:
var vEmailTarget = "mailto:" + xfa.datasets.DefineSurvey.Email.value
+ "?subject=" + xfa.datasets.DefineSurvey.SurveyTitle.value;
EmailSubmit.event__click.submit.target = vEmailTarget;
The other thing I did with submit was make use of the new event cancel capability. When the user clicks on the "submit" button, the pre-submit event fires. I put this script there:
xfa.host.messageBox("The survey is incomplete. Please fill in the highlighted questions.");
xfa.event.cancelAction = true;
The xfa.event.cancelAction property is new in Acrobat/Reader 9. It allows you to cancel the upcoming action in prePrint, preSubmit, preExecute, preOpen, preSign events.
The form makes extensive use of the validation framework I defined in previous blog entries — most notably, the exclusion group objects. The framework is contained in the three script objects at the top of the form: scValidation, scGeneral and scGroup. These are re-usable objects that can be used in forms where you want fine-tuned control over the form validation experience.
For those who have used previous versions of this framework, I added some enhancements to suit my needs for this sample:
New function: scValidate.hideErrors()
After this is called, any fields with errors are not highlighted until…
New function: scValidate.showErrors(subform)
This function causes error fields that are descendents of the input subform to be highlighted. If subform is omitted, errors are displayed on the entire form.
New function: getFirstError(subform)
Returns the first descendent field under the given subform that has an error. It subform is not specified, returns the first error field on the whole form.
Where targetField is a rich text field and the source is either a dataValue with rich text or another rich text field.
I also changed the code that highlights invalid fields. Instead of mucking with borders, I simply set the fill colour.