Dynamic Content

What is dynamic content? From PCProfiler's perspective, dynamic content is a template whose layout is not static. The 3ED&D Spell Sheet template that comes with PCProfiler is a perfect example. When you first use that template with a character, it is completely empty. Once you click the "Add Spell Slot" button, the first spell slot is added, and you go from there. That is, the layout is generated dynamically.

But how does it work in PCProfiler? From what you've learned so far, it shouldn't. PCProfiler saves data found in various FORM elements. So, when you save your document, all the data in the spell slot items will be saved by the program. Next time, when you load up your character, PCProfiler will render the blank template, then fill in all the slots with the saved data. But...when you load the blank template...there are no slots, so PCProfiler can't draw the saved data. So, how do you get it to work?

The answer lies in some creative scripting. Buckle up folks, we're going for a ride.

Generating the HTML code

Take a look at the source code for the 3ED&D Spell Sheet. It's very small, less than 2 kB. The bulk of this template is generated via scripts. To have a dynamic template, or have part of your template be dynamic, you're going to need to know how to generate blocks of HTML on the fly, and insert them into the current document. It's actually pretty easy, the key is to have a proper container to hold the dynamic content. The relevant code from the 3ED&D Spell Sheet template is highlighted below:
<DIV>
    <A onClick="AddSpellSlot()">
        <IMG src="./3ED&D Spell Sheet/add.jpg" border=0>
    </A>
</DIV>
 
<DIV id=Content>
</DIV>
There are two things to note here. First is the DIV element with the name (or id) of Content. This is the target element that will hold all the dynamically created HTML. Second, is the anchor which calls the AddSpellSlot() function from its onClick handler. Every time the user clicks on the imbedded image, one spell slot is added into the Content element.
function AddSpellSlot()
{
    // (Some code omitted for clarity)
    nNumSlots ++; // New amount of spell slots.
 
    // Add a slot to the bottom of the sheet.
    document.all.Content.innerHTML = document.all.Content.innerHTML
        + GenerateSlotHTML(nNumSlots);
}
The GenerateHMTL(nIndex) generates the HTML required for the spell slot, naming each element based off the number passed to the nIndex argument.

So that's how the code is dynamically created: with the innerHTML member of an appropriate element. But it still doesn't explain how the data gets preserved by 3EProfiler.

Data Mirroring

The key to getting dynamic content to work in PCProfiler is data mirroring. That is, every time you make a change to one of the dynamic inputs, that data is copied to a hidden input. Why? Because the hidden input is always there. Even on the "blank" template, if you look at the code, you'll find the hidden input. Take a look at this code snippet from the 3ED&D Spell Sheet template:
<FORM>
 
<INPUT type=hidden name=NumSlots>
<INPUT type=hidden name=CodedData>
Though you never see these two inputs, they are key to how the spell sheet template works. The NumSlots input keeps a running tally of the total number of spell slots the sheet has: every time AddSpellSlot() is called, the value of the hidden element is incremented. And the CodedData input is where all the data that is entered is mirrored to (in...surprise...a coded format). Since these two inputs are always there (they're not dynamically created), PCProfiler will save their values, and restore them when the template is reloaded from a *.pcp document. The key is being able to generate the data for the CodedData element.

The CData Class

This is a class I wrote to help with dynamic content. You can use it too by copying the Data.js file into your template's directory and indlucing the script in your file:
<SCRIPT language=JavaScript src="./MyFolder/Data.js">
The CData class is a wrapper around a scripting runtime dictionary object, so if you've ever used one, you'll probably feel at home. If you don't understand how the CData class, don't worry, you don't have too. All you need to know is how to use it. (If you're really interested in how the CData class works, lookup references on Object Oriented JavaScript programming.)

To use a CData object, you first need to declare one in your script:

myData = new CData();
The 3ED&D Spell Sheet template does this on line 9 of the dynamic.js file. You'll probably need to declare the CData object as global, since it will need to contain a copy of all data entered in the document.

You heard me right: the CData object will contain a copy of all data in the document, so that makes not two, but three copies of all data entered in the form that will be resident in memory. Inefficient? Yes. But it works.

CData contains four member functions that you will want to use.

SetValue(strName, strValue)
If the key strName doesn't exist, it is created and set to the value specified by strValue. If the key already exists, its value is set equal to strValue. Note any pipes characters are removed from strValue.
GetValue(strName)
Returns the value associated with the key strName. If the key is not found, undefined is returned.
GenerateDataString()
Returns a coded string of all data contained in the CData object. Each associative pair is seperated by a double pipes sequence ("||"), and each key/value pair is seperated by a single pipes character.
Example: key1|value1||key2|value2||key3|value3
CreateFromDataString(strData)
Recreates a the CData object from the coded string strData, and fills each element's value with appropriate data. Passing the value returned from a previous call of GetCodedData() will completely restore the CData object to its state when the GetCodedData() function was called. The name of each generated key is interpreted as the name to an element acceccible by the document.all collection, and its value is set to the corresponding value extracted from the coded data.
So, what good is all of this CData nonsense? The answer lies in each dynamic element's onChange event. Everytime the event is fired, the UpdateData() function is called, passing a pointer to the changed element:
<INPUT type=text
    name=MyDynamicElement01
    onChange="UpdateData(this)">
And the relevant script code is below:
myData = new CData(); // Global scope.
 
// ... (Code omitted for clarity)
 
function UpdateData(inText)
{
    // Update the CodedData input so all the info can be saved.
    myData.SetValue(inText.name, inText.value);
    document.all.CodedData.value = myData.GenerateDataString();
}
So, after each call to the UpdateData function, the CodedData hidden input contains an up to date record of all the dynamic data on the sheet (and the NumSlots contains the number of slots).

Regenerating Saved Data

So, we're almost there. We have all the data we need, hidden away in a coded form. How do we first of all regenerate the template to the proper size, then repopulate it with data?

The answer lies in the onLoad event. Before your script gets a chance to handle this event, PCProfiler has already loaded all the saved data from disk, populated all the template elements with their appropriate data. So, when the onLoad element fires, all your coded data has been restored to the hidden inputs. Beautiful!

First things first though, we need to arrange our loading function, Restore() to be called. No problemo!

<BODY oncontextmenu="return false" onLoad="Restore()">
Now, our Restore() function needs to do two things, generate the appropriate number of spell slots (from the value stored in the NumSlots element), then populate the slots with data (from the data stored in the CodedData element). The relevant code from the Restore() function is reproduced below:
function Restore()
{
    // ... (Code omitted for clarity)
    for (var i = 0; i < nNumSlots; i++)
    {
        document.all.Content.innerHTML
            = document.all.Content.innerHTML
            + GenerateSlotHTML(i + 1);
    }
    Populate();
    // ... (Code omitted for clarity)
}
The for loop simply adds the appropriate number of spell slots to the page, via the same function that AddSpellSlot() does. Then, the entire data set is recreated with a simple call to the Populate() function. What does it do? I'm glad you asked! It simply calls the CreateFromDataString() member function of our CData object. The CData object takes care of everything else!
function Populate()
{
    myData.CreateFromDataString(document.all.CodedData.value);
}

Breathe!

If this all seems very complicated, its because it is. Generating dynamic content is difficult enough, preserving it across sessions is even more difficult. So, if this all seems over your head, just sit down, relax, and don't forget to breathe. Generating a template like this demands a solid understanding of many different languages and concepts. HTML, CSS, JavaScript (or other scripting languages), Object Oriented Programming, are just a few things you need to wet your feet in. Once you get a good handle on these things, this whole dynamic content thingy will eventually fall into place.