Greg's Blog

helping me remember what I figure out

Working With and Storing Dynamic Forms

| Comments

Working with and storing dynamic forms

Just recently I was working on a project where a user was able to specify the number of form fields he wished to submit for a specific job run. This of course meant that the form had to be generated on the fly, these dynamic form fields had to then also be stored in a database and later on all fields should be available for viewing. This posed a number of problems all centering around these dynamic fields. How to capture, store and retrieve these, without knowing how may there were?

To show how I solved this problem we will be building a sample application that will allow the user to enter a collection of music albums associated to an artist. To this end you will need to create an MS Access database and code 2 Cold Fusion templates.

So let’s get started with the database. Create a database called record and add a table called artist to that database. For simplicities sake this table will only contain 2 fields, namely: artist_id (data type: auto-number) and artist_wddx (data type: memo). Next you will need to register a datasource called records. Right your database side of things is now done let’s look at the Cold Fusion templates.

First let’s create an application.cfm template (a bit excessive in this case, but it’s good practise none the less). This template will simply enable the session management and declare an application variable that will hold our datasource name (if you have set up your database with username and password access now is also a good time to include these here).

<cfapplication name=”records” sessionmanagement=”Yes” sessiontimeout=”#CreateTimeSpan(0, 4, 0, 0)#”>

<!— -Set datasource- —>
<cfparam name=”application.dsn” default=”records”>
<!— -Set database username and password if you swpecified one- —>
<cfparam name=”application.dBuser” default=”your username”>
<cfparam name=”application.dBpassword” default=”your password”>

Right time to move on to the main template document, the one that’s going to do all the hard work. As you recall from the introduction we need to overcome several issues here. First being how to store in a database the content of a form that has a dynamic number of fields. In this case we will be looking to store an artist’s name and all of his recordings. Now the problem here is evident, not every artist has released the same number of recordings.

The data entry process begins with the user specifying, the number of recordings he wishes to associate with the artist of his choice (the code is pretty well commented so you shouldn’t have any bother finding that segment). Submitting this form will result in the action parameter being set to show_form, which will once the browser has rendered the page result in a form appearing with a standard field, called artist name and then n number of rows to enter the recordings for that artist (where n is the number you specified on the previous form). The form also contains a hidden field that stores the number of rows entered. We will make use of that data when we come to outputting the content of the form again. Submitting this form will lead to inserting the data into the database.

Storage proved to be least of the problems. I opted to store these submissions as a WDDX packet in a database field, that way I solved the problem of the number of possible fields available in a database table. Consider that if you don’t know how many fields are going to be required by the user to complete the form, you can’t really design your database to match that number of dynamic fields. What you can however do is create a table that will store some information pertinent to the form and the content of the form as a WDDX packet. So submitting this form will result in all form fields being stored as a WDDX packet.

The next problem was actually creating the WDDX packet. All examples I found relied on a static set of variables that were to be put in a structure and then serialised. Scanning through the Advanced Cold Fusion Application Development book by Ben Forta and saw a segment on his custom tag CF_EmbedField. Rather than creating a new list of hidden input fields for a form I modified the code to dump all the form fields into a structure, which in turn could be serialised into a WDDX packet. Since the code loops over all form fields available after hitting the submit button, I can in turn capture, both the form name and the value of that field, store it in the structure before moving onto the next form element. You can see the code in action where the hdn_action parameter is set serialise.

OK, so now we have our structure and all we need to do is serialise into a WDDX packet which is described at length in the Advanced Cold Fusion Application Development book, so I shan’t say anymore on that topic. Now that we have our WDDX packet we can simply insert that into out database, et voila, you have circumvented determining the number of fields submitted and how to store these. In the following we shall now have a look at how you will extract information from that packet and display it.

The first thing we need to do is retrieve the WDDX packet from the database and then de-serialise it. Once this is done all the information is once again stored in a structure (called strAlbums). Another handy thing about using CF_EmbedField, is that it stores the old form field names in the structure so that when it comes to outputting the content you have an idea of what the fields where called and hence can reference them accordingly. The first we thing we output is the artist name. This form field was labelled txt_artist_name, using the array annotation (#strAlbums[‘txt_artist_name’]#) we can now access it’s value with very little effort. What about the dynamic rows though?

Remember I stored the number of rows required for thr form as a hidden input field? This field comes in handy now. This form field will help us determine the number of loops we are going to execute in order to display all the rows. The value was stored in the form field labelled: hdn_number_of_rows and hence using the aforementioned annotation : #strAlbums[‘hdn_number_of_rows’]#< we can retrieve it's value. Right now we know how many times we have to loop, but this still does not give us access to those form fields and their values. Whithin the loop I set two variables that are made up of the base form field names: ‘txt_record_name_’ and ‘txt_release_date_’ and appended the index value of the loop (i.e. 1,2 ,3…,n in turn depending on the number of rows stored). This gave the form field names as they appeared in the originally submitted form and exactly the same number of rows as before and these names are stored in: record_name and record_release respectively. We have come one step closer. Now we have the form field names but how do we get their values?

This is where we use the Cold Fusion function StructFind. Here we will be looking at the structure called strAlbums and in the first instance will be looking for the album name to be displayed. So as we loop through the list the first instance of record_name will be set to txt_record_name_1. StructFind will now search through the structure strAlbums for txt_record_name_1 (also known as a key) and return it’s value. It then does exactly the same thing for the release date, before continuing with the loop. Once the loop is completed you should now be presented with a table containing the artist name and all the submitted recordings and release dates.

And there you go, now you can store dynamic form fields in a WDDX packet, which in turn is stored in a database and by the same token you can access that packet, convert it back to a structure and display the forms content again without having to concern yourself with the number of fields submitted. All the code for this application can be found below and you can download the whole lot including database right here.

Code:

<!— -Before you use this page be sure to create a datasource called records pointing to the included database records- —>
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<!— -Declare some variables that will be frequently used- —>
<cfparam name=”hdn_action” default=”“>
<cfparam name=”message” default=”“>
<cfif hdn_action eq “serialise”><!— -If the form with the album details has been submitted do this- —>
  <cfset strAlbums = StructNew()><!— -Declare new structure- —>
    <cfif isDefined(“form.fieldnames”)><!— -Check that field names exists- —>
      <cfset fieldnames_processed = “”><!— -Create an empty list of processed variables- —>
      <cfloop index=”form_element” list=”#form.fieldnames#”><!— -Loop through field names- —>
        <cfif ListFind(fieldnames_processed, form_element) IS 0><!— -Try to find current element in list- —>
          <cfset form_element_qualified = “form.”&form_element><!— -Make fully qualified copy of it (to prevent accessing the wrong field type)- —>
          <cfset strAlbums[“#form_element#”] = ‘#trim(Evaluate(form_element_qualified))#’><!— -Store it in the structure- —>
          <cfset fieldnames_processed = ListAppend(fieldnames_processed, form_element)><!— -Add it to the processed list- —>
        </cfif>
      </cfloop>
    </cfif>
  <cfwddx input=”#strAlbums#” output=”MyWDDXPacket” action=”CFML2WDDX”><!— -Turn the structure into a WDDX packet- —>
  <cftransaction>
    <cfquery datasource=”#application.dsn#” name=”iDetails”><!— -Now store it in the database- —>
      INSERT INTO artist
      (artist_wddx)
      VALUES
      (‘#MyWDDXPacket#’)
    </cfquery>
    <cfset message= “Artist details stored stored.<br /><a href=’index.cfm’>Back</a>”><!— -Now set a result message- —>
    <cfwddx input=”#MyWDDXPacket#” output=”strAlbums” action=”WDDX2CFML”><!— -And de-seriliase it again (could be done easier now, but I wanted to show how it would be done if you pulled it out of the database - —>
  </cftransaction>
<cfelseif hdn_action eq “listing”><!— -Querie(s) to display the listing of stored records- —>
  <cfquery datasource=”#application.dsn#” name=”qGetAll”>
    SELECT artist_id, artist_wddx
    FROM artist
  </cfquery>
</cfif>
<html>
<head>
  <title>Album name storage</title>
</head>

<body>
<cfif hdn_action eq “show_form”>
  <form action=”index.cfm” name=”frm_number_of_rows” id=”frm_number_of_rows” method=”post” enctype=”multipart/form-data”>
  <table border=”0” cellpadding=”1” cellspacing=”0”>
    <tr align=”center”><td colspan=”3”>Artist/albums</td></tr>
    <tr align=”center”>
      <td colspan=”2” align=”right”>Artist name:</td>
      <td><input type=”Text” name=”txt_artist_name” id=”txt_artist_name” value=”“></td>
    </tr>
    <tr>
      <td> </td>
      <td>Record name:</td>
      <td>Release date:</td>
    </tr>
    <cfloop from=”1” to=”#form.txt_number_of_rows#” index=”counter”>
    <cfoutput>
      <tr>
        <td>#counter#. </td>
        <td><input type=”Text” name=”txt_record_name_#counter#” id=”txt_record_name_#counter#” value=”“></td>
        <td><input type=”Text” name=”txt_release_date_#counter#” id=”txt_release_date_#counter#” value=”“></td>
      </tr>
    </cfoutput>
    </cfloop>
    <input type=”Hidden” name=”hdn_number_of_rows” id=”hdn_number_of_rows” value=”<cfoutput>#form.txt_number_of_rows#</cfoutput>”><!— -Need to store this value so that we know how many times to loop when displaying the content- —>
    <input type=”Hidden” name=”hdn_action” id=”hdn_action” value=”serialise”>
    <tr align=”center”><td colspan=”3”><input type=”Submit” name=”btn_submit” id=”btn_submit” value=”Submit”></td></tr>
  </table>
  </form>
<cfelseif hdn_action eq “serialise”>
  <table>
    <tr><td><cfoutput>#message#</cfoutput></td></tr>
  </table>
  <!— -Time to show the content of the packet and determine the number of albums stored- —>
  <table border=”0” cellpadding=”0” cellspacing=”0” width=”300”>
    <tr align=”center”><td
colspan=”3”><b><cfoutput>#strAlbums[‘txt_artist_name’]#</cfoutput></b></td></tr>
    <cfloop from=”1” to=”#strAlbums[‘hdn_number_of_rows’]#” index=”counter”><!— -Number of loops required stored in structure- —>
      <!— -Set the variables to be evaluated against the structure (we know their names because the WDDX packet stored the form field names submmitted)- —>
      <cfset record_name = ‘txt_record_name_’&counter>
      <cfset record_release = ‘txt_release_date_’&counter>
      <!— -In order to display the value we make use of these variables listed above, which return a value like txt_record_name_1 and we search through the structure (in this case strAlbum) for this name. If found it displays the value.- —>
      <tr align=”center”>
        <td><b><cfoutput>#counter#</cfoutput></b></td>
        <td><cfoutput>#StructFind(strAlbums,record_name)#</cfoutput></td>
        <td><cfoutput>#StructFind(strAlbums,record_release)#</cfoutput></td>
      </tr>
    </cfloop>
  </table>
<cfelseif hdn_action eq “listing”>
  <cfoutput query=”qGetAll”><!— -Display all records stored in the database- —>
    <cfwddx input=”#artist_wddx#” output=”strAlbums” action=”WDDX2CFML”><!— -De-serialise the Wddx packet- —>
    <!— -Time to show the content of the packet and determine the number of albums stored- —>
    <table border=”0” cellpadding=”0” cellspacing=”0” width=”300”>
      <tr align=”center”><td colspan=”3”><b>#strAlbums[‘txt_artist_name’]#</b></td></tr>
      <cfloop from=”1” to=”#strAlbums[‘hdn_number_of_rows’]#” index=”counter”><!— -Number of loops required stored in structure- —>
        <!— -Set the variables to be evaluated against the structure (we know their names because the WDDX packet stored the form field names submmitted)- —>
        <cfset record_name = ‘txt_record_name_’&counter>
        <cfset record_release = ‘txt_release_date_’&counter>
        <!— -In order to display the value we make use of these variables listed above, which return a value like txt_record_name_1 and we search through the structure (in this case strAlbum) for this name. If found it displays the value.- —>
        <tr align=”center”>
          <td><b>#counter#</b></td>
          <td>#StructFind(strAlbums,record_name)#</td>
          <td>#StructFind(strAlbums,record_release)#</td>
        </tr>
      </cfloop>
    </table>
    <p></p>
  </cfoutput>
<cfelse><!— -The user specifies the number of recordings he wishes to associate with an artist.- —>
  <form action=”index.cfm” name=”frm_number_of_rows” id=”frm_number_of_rows” method=”post” enctype=”multipart/form-data”>
  <table border=”0” cellpadding=”0” cellspacing=”0”>
    <tr><td>Enter number of CDs you want to add for an atrist</td></tr>
    <tr><td><input type=”Text” name=”txt_number_of_rows” id=”txt_number_of_rows” value=”“></td></tr>
    <input type=”Hidden” name=”hdn_action” id=”hdn_action” value=”show_form”>
    <tr><td><input type=”Submit” name=”btn_submit” id=”btn_submit” value=”Submit”></td></tr>
  </table>
  </form>
  <a href=”index.cfm?hdn_action=listing”>View all stored albums</a>.<!— -Or alternatively can skip ahead and view the stored listings- —>
</cfif>

</body>
</html>