Programming forms and fields in a StoreFront template

Introduction

The UltraCart engine is a Java based checkout using a custom form processor.  We mention that because it's different from many popular templating systems.   As such, there will be some unfamiliar ways of naming form elements as well as some additional things a template form must do to make everything run smoothly.   The system is nearly two decades old, battle hardened, and lightning fast.   So, we apologize for the learning curve, but we trust you'll be able to overcome it with enough examples.

 

Simple Fields

For simple fields, name the form element the same as the $form property.   Use the $! notation to fill in the value of the form element.

Example: $form.address1

<input type='text' name='address1' value='$!form.address1' />   <!-- there's something wrong here... -->

You should use $!form.address1 instead of $form.address1.   The exclamation point instructs the renderer to output an empty string if the variable doesn't exist or is null.  It's a clean way to say "if there is a value, write it out".

The above example is suspect to cross-site scripting attacks. Whenever you output a variable in a template, you should consider whether that output should be html escaped.

The proper way to render the input field above is this:

<input type='text' name='address1' value='$i18n.escape($!form.address1)' />   <!-- this is safe. -->

The I18n object will escape any html properly to avoid attacks or page breakage.

 

Hidden Example

<input type='text' name='merchantId' value='$i18n.escape($!form.merchantId)' />


Checkbox Example

<input type="checkbox" name="mailingList" #if($form.mailingList)checked#{end}/>

The checkbox requires the checked attribute to be true, but must omit it completely for false.  The clean way to handle that is using an inline-if statement.

 

Textarea Example

<textarea id="comments" name="comments">$i18n.escape($!form.comments)</textarea>

 

Select Example

The select is more complex, because it will usually need a list of options.   Those options need to be iterated and the current option properly flagged with the selected attribute.

<select name="creditCardExpMonth">
 #foreach($expMonth in $form.creditCardExpMonths)
  <option value="$expMonth.value" #if(${formatHelper.equalsIgnoreCase($expMonth.value, $form.creditCardExpMonth)})selected#{end} >
   ## the first blank value should read 'Month' to help customers understand what this field is.
   #if ($expMonth.value == "")
    Month
    #else
    $expMonth.description
   #end
  </option>
 #end
</select>

 

 

Complex Fields

Complex fields extend the dot notation as far as needed to dive down into the object hierarchy.  Realistically, few of the UltraCart system forms extend below two levels.  When displaying and collecting a value from a nested object, fully reference the object and the engine will do the rest.

<input type='text' name='address.phone.mobile' value='$i18n.escape($!form.address.phone.mobile)' /> 

Working with collections of fields

In traditional http parameters, array fields all have the same name.

Traditional HTML

<input type='text' name='notifications' value='sms' /> 

<input type='text' name='notifications' value='email' /> 

<input type='text' name='notifications' value='chat' /> 

and the resulting url would be ?notifications=sms&notifications=email&notifications=chat

The big difference between traditional http cgi and the UltraCart engine is that the UltraCart engine treats all arrays as arrays of objects. To manage this, each array must provide its ordinal position, using a zero-based system.

UltraCart HTML

<input type='text' name='notifications[0]' value='sms' /> 

<input type='text' name='notifications[1]' value='email' /> 

<input type='text' name='notifications[2]' value='chat' /> 

or in an array of more complex objects:

<input type='hidden' name="items[0].itemId" value="BLUE_SHIRT"/>

<input type='hidden' name="items[0].quantity" value="1"/>
<input type='hidden' name="items[0].description" value="Blue Shirt"/>
<input type='hidden' name="items[0].amount" value="9.99"/>

<input type='hidden' name="items[1].itemId" value="BLUE_SHIRT"/>

<input type='hidden' name="items[1].quantity" value="1"/>
<input type='hidden' name="items[1].description" value="Blue Shirt"/>
<input type='hidden' name="items[1].amount" value="9.99"/>

<input type='hidden' name="items[2].itemId" value="BLUE_SHIRT"/>

<input type='hidden' name="items[2].quantity" value="1"/>
<input type='hidden' name="items[2].description" value="Blue Shirt"/>
<input type='hidden' name="items[2].amount" value="9.99"/>
Any time the ordinal positions are needed, you will always be iterating an array within the template.  Apache Velocity provides a very simply means of tracking the position within the array with the $foreach.index property. 

 

#foreach($item in $form.items)      
   #set($itemIndex = $foreach.index)
#end

It's a good practice to always assign the $foreach.index to a variable at the start of the loop to allow for easy management of nested loops.  

#foreach($item in $form.items)      
   #set($itemIndex = $foreach.index)
   <!-- set up hidden fields so the items object can be recreated on the server side. -->
   <input type='hidden' name="items[$itemIndex].itemId" value="$i18n.escape($!item.itemId)"/>
   <input type='hidden' name="items[$itemIndex].quantity" value="$i18n.escape($!item.quantity)"/>
              
   #foreach($option in $item.options)
      #set($optionIndex = $foreach.index)
      <input type='hidden' name="items[$itemIndex].options[$optionIndex].name" value="$option.name"/>
      <input type='hidden' name="items[$itemIndex].options[$optionIndex].value" value="$option.value"/>              
      $i18n.escape($option.name) : $i18n.escape($option.value)
   #end ##foreach- item options           
#end

 

Reconstructing the entire object to aid error handling

When updating a collection of objects, there is usually only a few fields that will actually change.  For example, with the auto_order.vm template, the only field in the $form.items array that is updated is the schedule field.   However, you should output all of the fields in the object into form elements (most of the time this will be hidden fields) for two reasons:

  1. When the object is submitted to the server, other fields, such as primary key fields, are needed to assign the updated fields to the right objects.
  2. There is an error handling layer that processes requests before the engine performs updates.  Any errors are sent directly back to the client.  By outputting the entire object, including display fields like 'description', the page can re-render fully when errors occur without having to go through the entire page load sequence on the server side.  

So, you'll notice unmodifiable fields often stored in hidden fields throughout examples.  These are there to aid server side object management and aid with error handling.