Login    Sites MenuBlueStep

BlueStep Platform Support

Relate Components
Outline full outline 
General Concepts/Getting Started 
The Relate Inspector 
Relate Structure 
Folders 
Forms 
Fields 
Option Lists and Groups 
Multi-Entry Reports 
Merge Reports* 
Formulas* 
End Points 
Wizards* 
Permission Report 
Other Elements and Functions* 
Using Relate Outside Relate* 
Design Patterns 
The compound form problem 

In some instances a single form cannot adequately represent what, to the end user, is a single data entry page.  One example is a form with a section that repeats for an unknown number of times.  Many of the physical therapy forms are like this with a main form for most of the data and a repeating, embedded form for range-of-motion and strength data corresponding to each joint that is being treated.  A second example is progress notes where there are many, many types of notes which all need to be reported together in chronological order, but which have a large number of unique fields for each note type.  In this case the fields needed for searching and organizing progress notes for reporting (such as the date/time of the note) need to be on one master form.  But a part of the form layout is imported dynamically from many separate forms depending on the unique requirements of each type of note.

What both of these examples have in common is a master form which represents to the user "the" form entry and one or more sub forms which are dynamically and seamlessly included into the layout of the master form.  This presents a particularly complex problem for a Relate developer.  There are many related problems which all must be solved to make this type of compound edit possible and make it appear seamless to the end user, particularly during the creation of the composite parts.  There are 7 steps to solving this problem outlined below:

1. Setting up the forms.  The master form must include any fields needed for looking up an entry for reporting such as the date/time of the entry and fields to categorize and summarize the compound form entry.  The sub-form must include a hidden text field containing the id of the master form and/or a hidden relationship field to the master form.  Most current examples primarily use an id field, but dynamic merge tags have recently made a relationship field practical and perhaps cleaner.  The master form is the form seen by the end users and is usually included in navigation.  The sub-form is always hidden in navigation.  Both forms are multi-entry.  Eventually the master form will include a merge-report field to display and edit fields from the sub-form entries, but the merge report cannot be fully implemented until step 3.

2. Initializing the compound form's components.  All of the sub-form entries must be created and associated with the master form during the creation of a new entry.  This is done in at least two phases.  a. First there must be a formula which creates the master form entry and redirects the browser to edit that entry.  b. Then there is a post-save formula on the master form entry which creates the sub-form entries and associates them with the master form entry.  These two formulas are discussed below.

2a. Creating the master entry and redirecting.  The simplest to understand variation of this formula is an endpoint.  The end point receives the id of the record where the form is being created.  It looks up the record to work with, creates an entry of the master form, commits the transaction (so the id and URL of the newly created master-form entry is available), and redirects to the new master-form entry.  A typical example script is as follows:

id = request.getParameter("_id");
record = allRecords.getById(id);
entry = record.masterForm.System.getNewEntry();
transaction.commit();
response.redirect(entry.System.viewURL);

The endpoint is secured by setting the security so only users authorized to create an entry of the master form are allowed to access the endpoint.  For sensitive or private data HTTPS is made mandatory. 

A second, more complex variation of this theme is used by progress notes:  There is a single entry form where the type of progress note to be created is configured.  There is a pre-save formula on this single-entry configuration form which creates the master form entry, configures it according to the options selected and redirects to a merge-report (not shown in navigation, no primary form, found using getRecordNav() and searching by name).  During step 2b. the newly created master-form entry puts its own id into a user-data field (Example: getUserData(false).set("anyUniqueName", masterForm.System.id);).  The merge report pulls the id of the master form entry from user-data, looks it up and displays it with a dynamic merge-tag (Example: result = allMasterForm.getById( getUserData(false).removeFirst("anyUniqueName") ).getMergeTag();).

It is also possible to initialize sub-form entries on-the-fly as the edit proceeds and as directed by the end-user.  The basic technique involves Javascript, triggered by some user action, which sends data (including the id of the master form entry) to an endpoint.  The end point creates sub-form entries, connects them to the master form entry and initializes any other settings needed.  Then the page is refreshed via javascript (such as the genericRefresh() function) which causes the newly created sub-form entry to show up in the layout.  This technique is very new so a "best-practices" pattern has not been established.  Ask Ammon for details.

2b. Creating the sub-form entries.  This is always a post-save formula with the primary form set to the master-form.  A typical formula has four steps:  check to see if the master-form entry is just created, decide what sub-form entries need creating, create the sub-form entries, connect them to the master form entry.  Note that generally an id field and a relationship field would not both be initialized as shown.  They are both included for completeness.  Here is the example code:

if (masterForm.System.justCreated) {
    // decide what sub entries need creating and for each entry do something like this:
    subEntry = subForm.System.getNewEntry();
    subEntry.masterId = masterForm.System.id;
    subEntry.masterRelationship.set(masterForm.System.id);
    // initialize any other settings of subEntry

    // do any other logic required by step 2a (second variation) and/or step 6 (on-demand variation).
}

3. The compound layout.  The compound layout is accomplished using a merge-report field on the master which includes the form entries of the sub-form.  The most common example is the "triple-stack" which consists of the outer merge-report which includes a multi-entry form report which has a merge-report display column which shows each sub-entry.  Dynamic merge tags make dozens of other variations possible which are generally simpler to code but harder to design since there are so many choices.  Only the triple-stack technique will be described here.  The triple-stack is built from the inside out, so the first component is a merge report which displays a single entry of the sub-form.  Next the multi-entry is built which displays the sub-form merge report.  Last the outer-merge report is built to include the multi-entry report.

3a.  The inner merge-report.   The inner report has a primary form of the sub-form.  It generally is setup to edit data.  Sometimes all that is needed is to include the generic layout of the sub-form, but more often a custom layout is created and the sub-form's edit-fields are inserted into the layout.

3b. The multi-entry report.  A multi-entry report is needed with its primary form set to the sub-form.  Usually no search criteria are needed.  The display column(s) are generally just the merge-report created in step 3a and any fields required to sort the entries.  The sorting is usually done by adding grouping so the sort columns to not take up any horizontal space.

3c. The outer merge-report.  The outer merge-report has its primary form set to the master-form.  It generally is setup to edit data.  A formula is required to assure that only sub-form entries connected to the master form are displayed.  Then the multi-entry report is included in step 4 of the configuration wizard.  The formula needs the current entry of the master form (we'll call it "cur") and the multi-entry form report of the sub-form.  The formula has two statements:  subFormList.addSearch("masterId", "=", cur.System.id); subFormList.rememberSearchAndSort();  Once the outer-merge-report is created it is included into the master-form via a merge-report field.

4. The new entry button.  When the user clicks the "New Entry" button on the master form it goes to a new entry which has not been properly initialized with connections to sub-form entries.  To avoid this problem, when the user clicks the new entry button they must be redirected to the formula that creates and initializes a master form entry (usually the endpoint described in step 1a.).  There is only one formula which is guaranteed to run when the new entry button is clicked and at NO other time.  That is the formula of the merge report described in step 3 (3c if using the triple stack technique).  The formula in the merge-report needs logic which reads:

if (cur.System.justCreated) {
    sendRedirect(/* endpoint url goes here.  Don't forget to pass the record id */)

5. The delete button.  When an entry of the master form is deleted, the sub-form entries need to be cleaned up as well.  This is done with a pre-delete formula with the primary form set to the master form.  The sub-form entries are found using pretty much the same technique used to display them in the outer merge-report of step 3.  Just go through the entries and delete them.  Assuming the some variation of the triple-stack technique the code would look like this:

subFormList.addSearch("masterId", "=", cur.System.id);
for (i, subForm in subFormList) {
    subForm.System.deleteEntry();

6. The cancel button.  You may have noticed that in order to connect everything together this technique requires everything to be saved to the database so it has an id to reference when establishing the connections.  This presents a problem if the end user changes their mind after creating an entry since the mostly-blank data is already stored in the database.  If the user leaves, there may be absolutely no way to detect it.  What if they just switch off their computer or close their browser, for instance?  The BlueStep server is usually not notified when the user leaves.  So how do we clean it up when they leave without saving?   The short answer is, we can't.  However, we can assume that if they haven't saved it within, say, 4 hours then they probably aren't going to.  The second problem is how to prevent these not-complete entries from showing up in reports and listings before they are completed and saved by the user.  The following technique is recommended:  Create a hidden boolean field on the master-form called "Hidden" or something similar.  Give it a field formula that reads something like hidden = System.justCreated;  This will make the boolean true when the system creates, initializes and stores the master form entry, then it will go to false the first time the end user saves it.  Make sure that any reports or listings seen by the end user exclude entries that are marked hidden. That takes care of the second half of the problem.  To clean up abandoned entries a formula needs to run at least an hour or two after the form entry is created and delete the entry if it is still marked hidden.  There are two possibilities:  A scheduled formula or an on-demand formula.  A nightly formula needs to find hidden entries that were not created recently and delete them.  The code would look something like this:

allMasterForm.addSearch("hidden", "=", true);
for (i, entry in allMasterForm) {
    if (entry.System.createdTimestamp.calc("PT2H") < curDateTime()) {
        entry.System.deleteEntry();
    }
}

The variation with an on-demand formula requires two parts:  Some formula must schedule the on-demand formula, the on-demand formula must decide whether to delete.  An ideal place to schedule the on-demand formula is in step 2b.  Just add a line to that formula that reads:  masterForm.System.runFormula("myOnDemandFormula", toDateDiff("PT2H"));  Then create the on-demand formula with a matching id and the primary form set to the master form.  The formula gets the current master form entry, checks to see if it is still marked hidden and deletes it:

if (cur.hidden) {
    cur.System.deleteEntry();

7. Locking the data.  A problem often occurs that these types of complex compound forms are too big to be filled out all in one session.  Even if they can be filled out all at once, since the data must be pre-created and stored, the user must have Editor permission just to fill out the form the first time.  The problem of how to lock the data once it has been entered cannot be done with permissions, via the Creator permission level, as it can with normal form data.  Usually the most practical method is via conditional locking and hiding.  Unfortunately this must be configured field-by-field until a feature is developed to make it faster.  Also, since the data is spread across multiple forms and conditional field-locking only works within a single form entry at a time, both the master form and all sub-forms must have a locking field and these fields must be coordinated to all lock and unlock together.  Usually this is done from the master form entry which then controls the sub-form entries.  The actual code is pretty simple and very similar to the delete code described in step 5, so it won't be listed here.  But the basics are: look-up all the sub-entries and set each sub-entry's lock field the same as the master entry.