TQS Home
Trivia Quiz Shell Version 2.8 Now Available!Bot Productions Home
 

Part X: Advanced Customization

Up until this point, you have been learning how to use built-in TQS features. This section of the tutorial is aimed at developers who have experience in HTML and JavaScript (also known as JScript).

This section of the tutorial will define and add a "MATCHING" location type, which will present the users with a term, with which they will have to match the corresponding term from another list. To begin our customization, we add the CUSTOM element to the TQS element, as follows:

<?xml version="1.0"?>
<TQS VERSION="2.0" EDITABLE="1">
  <TITLE>My TQS Application</TITLE>
  <AUTHORINFO ... />
  <TQSWINDOW ... />
  <GLOBAL>...</GLOBAL>
  <LOCATIONS START="0">...</LOCATIONS>
  <CUSTOM>
  </CUSTOM>
</TQS>

All of the information defining custom location types must be contained in the CUSTOM element. We add an individual custom location type through the CUSTOMDIV element. Its NAME attribute specifies our name of the location type; it can be named anything but must be prefixed with CUSTOM_. The name of the location type is what you specify in a LOC element's TYPE attribute.

This is how our CUSTOM element will look so far:

<CUSTOM>
  <CUSTOMDIV NAME="CUSTOM_MATCHING">
  </CUSTOMDIV>
</CUSTOM>

The CUSTOMDIV element only specifies the code for the location type; it will not provide any data. Data is still stored in the LOC element; in our case, in the CUSTOM_MATCHING subelement. Before we write any code for our location type, we should design how we want to store our data. For matching, we will use the following structure:

<LOC ID="0" TYPE="CUSTOM_MATCHING">
  <HEADING>Custom Location Example<HEADING>
  <CUSTOM_MATCHING>
    <ITEM
      Q="The capital of Wisconsin"
      A="Madison"
    />
    <ITEM
      Q="The capital of Minnesota"
      A="St. Paul"
    />
    ...
  </CUSTOM_MATCHING>
</LOC>

An ITEM element contains a single pair of matched attributes. The A attribute specifies the answer to be displayed in the list of all answers, while the Q attribute displays the corresponding question.

TQS uses HTML DIV elements to display a Location. Thus, before adding any code to your TQS application you may want to experiment with a regular HTML file first, to determine the exact HTML code.

The DIVCODE element, which goes inside of your CUSTOMDIV element, contains the HTML code of your location type. You must replace all greater-than signs with &gt; and all less-than signs with &lt;. This is rather tricky, so it may be easiest to type it in normally and then use find and replace. Also, you should put all of your HTML code in one line in your TQS document, to ensure that it displays correctly. The following code splits it up, but you would not want to:

<CUSTOMDIV NAME="CUSTOM_MATCHING">
  <DIVCODE>
    &lt;SPAN ID="matchingChoicesSpan"&gt;&lt;
    /SPAN&gt;&lt;H3 ID="matchingQuestionSpan"&
    gt; &lt;/H3&gt;&lt;H3
    ID="matchingStatus"&gt; &lt;/H3&gt;&
    lt;SPAN ID="bottomButtons"& gt;&lt;BR&gt;
    &lt;INPUT TYPE=Button ID="matchNextButton"
    VALUE="Next Question"
    onclick="loadMatchQuestion(0);"
    STYLE="visibility:hidden"&gt;&lt;/SPAN&gt;
  </DIVCODE>
</CUSTOMDIV>

This translates into the following HTML:

<SPAN ID="matchingChoicesSpan"></SPAN><H3
ID="matchingQuestionSpan"></H3><H3
ID="matchingStatus"> </H3><SPAN
ID="bottomButtons"><BR><INPUT TYPE=Button
ID="matchNextButton" VALUE="Next Question"
onclick="loadMatchQuestion(0);"
STYLE="visibility:hidden"></SPAN>

In TQS 2.1 or newer, you do not have to replace all of the greater and less than signs, as long as the code you insert is XML compliant. To specify this, add the XMLCOMPLIANT attribute to the DIVCODE element and set it equal to "1".

Note: Prior to TQS 2.8, all custom-named objects, including HTML IDs, script functions and variable names, should start with S_. This was to ensure compatibility with the internal structure of TQS and with other custom location types. As of TQS 2.8, this is no longer required, as TQS uses a new mechanism to perform this isolation.

In our example, the matchingChoicesSpan HTML element will be filled with available answers; the matchingQuestionSpan element will contain the current question; matchingStatus will tell if the answer is correct or incorrect; and the bottomButtons span will contain the matchNextButton button, used to go on to the next question.

The inner workings of the HTML and JavaScript code used in this example are beyond the scope of this tutorial; however, a brief explanation will be given.

TQS currently only supports the use of JavaScript (JScript). All of your script code goes in the SCRIPT element, a child of the CUSTOMDIV element. Also, the CUSTOMDIV element contains the ONLOAD element. This contains JavaScript command(s) and is executed when your custom location is loaded. It gives you a chance to set up your location, usually by calling a function which appears in your SCRIPT element code. The code below shows use of the SCRIPT and ONLOAD elements:

<CUSTOMDIV NAME="CUSTOM_MATCHING">
  <DIVCODE> ... </DIVCODE>
  <ONLOAD>loadMatchQuestion(1);</ONLOAD>
  <SCRIPT>
  var canAnswerMatch = -1;
  var curMatch = -1;
  var curMatchIndex = -1;
  var matchCorrect = -1;
  var matchQuestionsUsed = new Array();
  var matchScrambleMap = new Object();
  var matchBackScrambleMap = new Object();
  var matchCorrectString = "Correct!"
  var matchWrongString = "Incorrect!"

  // loadMatchQuestion
  // Loads the next matching question
  // Parameters:
  //   first: if 1, will initialize entire Matching series,
  //   otherwise, just this question

  function loadMatchQuestion(first)
  {
    if(first)
    {
      xmlTemp = xmlChildOf(xmlLocData, "CORRECT", 0);
      if(xmlTemp)
        matchCorrectString = xmlTemp.text;
      xmlTemp = xmlChildOf(xmlLocData, "INCORRECT", 0);
      if(xmlTemp)
        matchWrongString = xmlTemp.text;
      matchingChoicesSpan.innerText = "";
      curMatch = -1;
      matchQuestionsUsed.length = xmlNumChildOf(xmlLocData,
        "ITEM");
      matchScrambleMap.length = matchQuestionsUsed.length;
      matchBackScrambleMap.length = matchQuestionsUsed.length;
      for(i=0; i &lt; matchScrambleMap.length; i++)
      {
        matchScrambleMap[i] = -1;
        matchBackScrambleMap[i] = -1;
      }
      for(i=0; i &lt; matchScrambleMap.length; i++)
      {
        oneToScramble = -1;
        do
        {
          oneToScramble = Math.round(
            (matchQuestionsUsed.length) * (Math.random()));
        } while (matchScrambleMap[oneToScramble] != -1);
        matchScrambleMap[oneToScramble] = i;
        matchBackScrambleMap[i] = oneToScramble;
      }
      for(i=0; i &lt; matchQuestionsUsed.length; i++)
      {
        matchQuestionsUsed[i]=0;
        matchingChoicesSpan.insertAdjacentHTML("BeforeEnd",
          '&lt;SPAN CLASS="matchNormal" ID="matchChoice'
          + matchScrambleMap[i]
          + '" onmouseover="onMatchEvent(0, '
          + matchScrambleMap[i]
          + ');" onmouseout="onMatchEvent(1, '
          + matchScrambleMap[i]
          + ');" onclick="onMatchEvent(2, '
          + matchScrambleMap[i]
          + ');"&gt;' + xmlAttr(xmlChildOf(xmlLocData,
            "ITEM", matchScrambleMap[i]), "A", "DEFFF")
          + '&lt;/SPAN&gt;&lt;BR&gt;');
      }
    }
    matchToLoad = 0;
    do
    {
      matchToLoad = Math.round((matchQuestionsUsed.length-1)
        * (Math.random()));
    } while (matchQuestionsUsed[matchToLoad] == 1);
    curMatchIndex = matchToLoad;
    curMatch++;
    matchNextButton.style.visibility = "hidden";
    matchingStatus.innerText = " ";
    matchingQuestionSpan.innerText = xmlAttr(xmlChildOf(
      xmlLocData, "ITEM", matchToLoad), "Q", "");
    canAnswerMatch=1;
  }

  // onMatchEvent
  // Handles the mouse hovering over or selecting an item
  // Parameters:
  //   evt: 0 means mouse is hovering over item, 1 means no
  //   longer hovering, 2 means item was clicked

  function onMatchEvent(evt, choice)
  {
    if((canAnswerMatch == 1) &amp;&amp; (choice &lt;
      matchQuestionsUsed.length) &amp;&amp;
      (matchQuestionsUsed[choice] == 0))
    {
      switch(evt)
      {
        case 0:
          document.all["matchChoice" + choice].className =
            "matchOver";
          break;
        case 1:
          document.all["matchChoice" + choice].className =
            "matchNormal";
          break;
        case 2:
          document.all["matchChoice" + choice].className =
            "matchNormal";
          matchQuestionsUsed[curMatchIndex] = 1;
          canAnswerMatch = 0;
          if(curMatchIndex == choice)
          {
            matchCorrect++;
            matchingStatus.innerText = matchCorrectString;
          } else
          {
            matchingStatus.innerHTML = matchWrongString +
              xmlAttr(xmlChildOf(xmlLocData, "ITEM",
              curMatchIndex), "A", "") + ".";
          }
          document.all["matchChoice"+curMatchIndex].className
            = "matchGray";
          if(curMatch &lt; matchQuestionsUsed.length - 1)
          {
            matchNextButton.style.visibility = "visible";
          }
          break;
      }
    }
  }
  </SCRIPT>
</CUSTOMDIV>

Note: Once again, greater-than and less-than signs must be coded throughtout your script as &gt; and &lt;, respectively. Also, ampersands (&) must be coded as &amp;.

The script above will not be covered in detail; however, some there are some important things to point out.

The script contained in the ONLOAD element is what will be executed when your location type is loaded. In the case above, we set up our variables and load the first question. Using Dynamic HTML and script, we have coded a complete matching game.

How does our script get the data from the LOC element's CUSTOM_MATCHING element? TQS provides several functions and variables for you to use to get this data.

The XML structure you use to organize your data inside the LOC element is up to you. TQS provides the variable xmlLocData, which represents the CUSTOM_* element in the current LOC element. Using this variable in conjunction with the following script functions gives you access to the data.

The functions TQS provides are xmlNumChildOf, xmlChildOf, xmlAttr, and xmlRetrieve. For information on how to use these functions, see the Reference. (Additional functions are available with TQS 2.5 and are discussed in Part XIV of this tutorial.)

You may notice that in the example script, several HTML elements' className property are changed. This is to change the appearance of an item, to states such as hovering or grayed. TQS allows you to add CSS (cascading style sheet) entries to the TQS application, using the following syntax:

<CUSTOMDIV NAME="CUSTOM_MATCHING">
  <DIVCODE> ... </DIVCODE>
  <ONLOAD> ... </ONLOAD>
  <SCRIPT> ... </SCRIPT>
  <STYLES>
    <STYLE
      NAME=".matchNormal"
      STYLEENTRY="cursor:hand; font-size:larger;
        color:gray"
    />
    ...
  </STYLES>
</CUSTOMDIV>

The STYLES element, a child of the CUSTOMDIV element, contains a series of STYLE elements. The NAME attribute specifies the CSS selector, which specifies which HTML elements the rule applies to. This is set to .matchNormal, which affects elements whose class is matchNormal. You can only specify custom classes through this method (that is, classes starting with ).

Various CSS attributes can be set through the STYLEENTRY attribute, just like you would in a regular CSS entry. For more information, see the Reference.

You have now been given the background on how to use advanced customization techniques to create your own TQS location types. This concludes the TQS 2.0 tutorial; the next five parts contain information on new features in TQS 2.1, 2.5, and 2.6.

Part XI: HTML Locations and the Data Root


©2016 Bot Productions. All rights reserved.Last Updated: September 9, 2007