Table of contents

    COMPLETE COOKBOOK ON A SINGLE PAGE
ANSWER CATEGORIES RECIPES
    A1 Randomizing the answer categories
    A2 Displaying only some answer categories
    A3 Displaying only some entries in a large list of answer categories
    A4 Randomly selecting a subset of possible answer categories
    A5 Randomly selecting a subset of selected answer categories
    A6 Adding an initial message to a drop-down list
    A7 Creating a question requesting a value and a unit
    A8 Adding headers in answer sets
    A9 Ensuring that category permutations are the same for two questions
    A10 Drawing answer categories from an external source
TABLE APPEARANCE RECIPES
    B1 Keeping the table columns the same width
    B2 Inserting an other box in a table
    B3 Displaying semantic differential scales
    B4 Using the top left corner of the table
OTHER QUESTIONNAIRE APPEARANCE RECIPES
    C1 Modifying the appearance of recalled text
    C2 Displaying different text upon recall of an answer
    C3 Controlling where to show the progress bar
    C4 Formatting the progress bar
    C5 Formatting buttons, boxes and dropdown lists
    C6 Adaptative button text
    C7 Adapting to mobile devices
QUESTIONNAIRE FLOW RECIPES
    D1 Displaying questions in random order
    D2 Ending a questionnaire without an exit URL
    D3 Exiting to a different URL depending upon circumstances
    D4 Suspending a questionnaire and sending a return e-mail
    D5 Displaying a question only at the call centre
    D6 Displaying a question in only one language
    D7 Testing that responses total 100%
    D8 Changing to another language programmatically
    D9 Branching out to any URL
CALCULATIONS RECIPES
    E1 Determining how many responses were given to a question
    E2 Determining how many responses were given to a series of questions
    E3 Adding a specialized function to CallWeb
    E4 Bringing data to a common unit
    E5 Calculating time spent on a page
    E6 Debugging a complex calculation
    E7 Recalculating a CALCUL question en masse
    E8 Verifying an e-mail address
    E9 Adding or subtracting time from a date-time value
    E10 Drawing a data chart
ACCESS AND SECURITY RECIPES
    F1 Closing data collection at a particular date or time
    F2 Controlling access to a questionnaire by IP address
    F3 Closing access once a certain number of completed questionnaires is reached
    F4 Giving restricted access to some individuals
    F5 Password-protecting a directory
    F6 Providing short links to questionnaires
    F7 Offering an "Unsubscribe" mechanism
    F8 Setting up access with a modifiable password
    F9 Two-factor authentication and more access control
    F10 Controlled access on an open project
SPECIAL ACTIONS
    G1 E-mailing during the course of the questionnaire
    G2 Creating an open-end part allowing only unique values
    G3 Recording a Web completion as part of a dual-mode project
    G4 Activating the CallWeb pretest mode and accepting respondent feedback
    G5 Coding open-end questions
    G6 Managing a do-not-call list in CATI mode
    G7 Naming the sending e-mail address
    G8 Recording face-to-face interviews
    G9 Pushing data into a project during an interview
    G10 Identifying straightlining
    G11 Preparing a large prepop
    G12 Commenting a questionnaire

Table of contents

    COMPLETE COOKBOOK ON A SINGLE PAGE
ANSWER CATEGORIES RECIPES
    A1 Randomizing the answer categories
    A2 Displaying only some answer categories
    A3 Displaying only some entries in a large list of answer categories
    A4 Randomly selecting a subset of possible answer categories
    A5 Randomly selecting a subset of selected answer categories
    A6 Adding an initial message to a drop-down list
    A7 Creating a question requesting a value and a unit
    A8 Adding headers in answer sets
    A9 Ensuring that category permutations are the same for two questions
    A10 Drawing answer categories from an external source
TABLE APPEARANCE RECIPES
    B1 Keeping the table columns the same width
    B2 Inserting an other box in a table
    B3 Displaying semantic differential scales
    B4 Using the top left corner of the table
OTHER QUESTIONNAIRE APPEARANCE RECIPES
    C1 Modifying the appearance of recalled text
    C2 Displaying different text upon recall of an answer
    C3 Controlling where to show the progress bar
    C4 Formatting the progress bar
    C5 Formatting buttons, boxes and dropdown lists
    C6 Adaptative button text
    C7 Adapting to mobile devices
QUESTIONNAIRE FLOW RECIPES
    D1 Displaying questions in random order
    D2 Ending a questionnaire without an exit URL
    D3 Exiting to a different URL depending upon circumstances
    D4 Suspending a questionnaire and sending a return e-mail
    D5 Displaying a question only at the call centre
    D6 Displaying a question in only one language
    D7 Testing that responses total 100%
    D8 Changing to another language programmatically
    D9 Branching out to any URL
CALCULATIONS RECIPES
    E1 Determining how many responses were given to a question
    E2 Determining how many responses were given to a series of questions
    E3 Adding a specialized function to CallWeb
    E4 Bringing data to a common unit
    E5 Calculating time spent on a page
    E6 Debugging a complex calculation
    E7 Recalculating a CALCUL question en masse
    E8 Verifying an e-mail address
    E9 Adding or subtracting time from a date-time value
    E10 Drawing a data chart
ACCESS AND SECURITY RECIPES
    F1 Closing data collection at a particular date or time
    F2 Controlling access to a questionnaire by IP address
    F3 Closing access once a certain number of completed questionnaires is reached
    F4 Giving restricted access to some individuals
    F5 Password-protecting a directory
    F6 Providing short links to questionnaires
    F7 Offering an "Unsubscribe" mechanism
    F8 Setting up access with a modifiable password
    F9 Two-factor authentication and more access control
    F10 Controlled access on an open project
SPECIAL ACTIONS
    G1 E-mailing during the course of the questionnaire
    G2 Creating an open-end part allowing only unique values
    G3 Recording a Web completion as part of a dual-mode project
    G4 Activating the CallWeb pretest mode and accepting respondent feedback
    G5 Coding open-end questions
    G6 Managing a do-not-call list in CATI mode
    G7 Naming the sending e-mail address
    G8 Recording face-to-face interviews
    G9 Pushing data into a project during an interview
    G10 Identifying straightlining
    G11 Preparing a large prepop
    G12 Commenting a questionnaire

   

Randomizing the answer categories

Problem

You want to randomize the order of presentation of choices of answers to a question.

Solution

Add the ROTATION keywork to the question name line.

Discussion

The ROTATION keyword added anywhere on the question name line (except in the first position, obviously, as that one is reserved to the question name) tells CallWeb to shuffle the display of answer categories in random order. However, categories with a code greater or equal to 900 are always left at the end of the list of answer categories and are not subjected to randomization; this way, "Don't know" type answers can remain at the end of the list of choices.

Answer choices can be randomized in the same manner when questions are laid out in table format. The order of presentation of the entire table is then driven by the order selected for the first question in the table.

Randomizing the answer categories

Problem

You want to randomize the order of presentation of choices of answers to a question.

Solution

Add the ROTATION keywork to the question name line.

Discussion

The ROTATION keyword added anywhere on the question name line (except in the first position, obviously, as that one is reserved to the question name) tells CallWeb to shuffle the display of answer categories in random order. However, categories with a code greater or equal to 900 are always left at the end of the list of answer categories and are not subjected to randomization; this way, "Don't know" type answers can remain at the end of the list of choices.

Answer choices can be randomized in the same manner when questions are laid out in table format. The order of presentation of the entire table is then driven by the order selected for the first question in the table.

   

Displaying only some answer categories

Problem

You want to display only some of the answer categories attached to a question, according to other survey responses.

Solution

Insert an answer category display condition such as:

    [CITY.EQ.5]
    Globe and Mail

Discussion

Each answer category of a question may be assigned a display condition. If one is, the answer category is displayed only if the condition is "true". In the example above, the choice "Globe and Mail" is displayed only if question/field CITY equals 5.

Answer category display conditions are independent from one another:

  • one category may be given one while another cateogry of the same question may have none (and always be displayed);
  • each display condition may use different criteria and may use x-base syntax or Perl syntax.

When questions are presented in table format, the answer categories of the first question in the table define the columns of the table, even if the answer categories bear display conditions. However, the radio button or checkbox or text box corresponding to an answer category which has a "false" display condition is left out of the table (i.e., the corresponding table cell is left empty).

Displaying only some answer categories

Problem

You want to display only some of the answer categories attached to a question, according to other survey responses.

Solution

Insert an answer category display condition such as:

    [CITY.EQ.5]
    Globe and Mail

Discussion

Each answer category of a question may be assigned a display condition. If one is, the answer category is displayed only if the condition is "true". In the example above, the choice "Globe and Mail" is displayed only if question/field CITY equals 5.

Answer category display conditions are independent from one another:

  • one category may be given one while another cateogry of the same question may have none (and always be displayed);
  • each display condition may use different criteria and may use x-base syntax or Perl syntax.

When questions are presented in table format, the answer categories of the first question in the table define the columns of the table, even if the answer categories bear display conditions. However, the radio button or checkbox or text box corresponding to an answer category which has a "false" display condition is left out of the table (i.e., the corresponding table cell is left empty).

   

Displaying only some entries in a large list of answer categories

Problem

You have a large list of possible answer categories and you want to display only a relevant subset of them to the respondent.

Solution

Use the SUBSET keywork or the CODESIN keyword on the question name line.

Discussion

SUBSET

There are times when there are too many answer categories to show them all on-screen (e.g., a list of all municipalities in the country). The list of categories can then be reduced by using the SUBSET feature. When this feature is activated, only categories with a label containing certain characters are displayed. Here is how this works.

First, create a question with an open-end category that collects the few characters to search for. For example, insert a question requesting that the respondent enters a few characters of the name of the municipality where they reside; this question could be named "CHARS_OF_CITYNAME".

Second, create a regular question containing all possible answer categories (like all city names). Add other categories that you want to display all the time (e.g., Other, DK/NR) as required and give them an "F" behaviour (for "force display"). Add the keywork SUBSET= on the question name line of the question definition and complete it with the name of the variable containing the characters to search for, as in SUBSET=CHARS_OF_CITYNAME.

That's it. Upon using the questionnaire, only categories containing the few characters entered in the first question will be displayed when the second question is called up. Of course, these two questions cannot be part of the same screen as the formatting of the second requires that the answer from the first be available. The two questions need not immediately follow each other.

SUBSET type questions otherwise behave exactly like any other question. Therefore, a display condition can be applied to display the SUBSET question only when the initial question requesting a few characters to subset on has been correctly answered (and not left, for example, on a "Don't know").

CODESIN

The CODESIN= parameter of the question name line also performs a selection in the answer categories of the question to which it is attached. In this case, the only categories shown are those whose code is found in the open-end part named after CODESIN=. In the open-end part, the list of codes must be separated with "mu" characters (µ).

For example, the following code extracts part numbers from one data base based on price and displays only those part numbers in the follow-up question:

    PARTNUMBERS CALCUL
    % Text
      APARTNUMBERS = pull_value("UNIQUE","PARTNUMBER","PARTNUMBER_DB","APRICE > 10");
    % Note
    % Categories
     
    % Simple skips
    % Display condition
    % Open parts
      1 = C100
    ! ===================================================
    WHICHPART CODESIN=APARTNUMBERS
    % Text
      Select the appropriate part number
    % Note
    % Categories
    % Simple skips
    % Display condition
    % Open parts
    ! ===================================================

Displaying only some entries in a large list of answer categories

Problem

You have a large list of possible answer categories and you want to display only a relevant subset of them to the respondent.

Solution

Use the SUBSET keywork or the CODESIN keyword on the question name line.

Discussion

SUBSET

There are times when there are too many answer categories to show them all on-screen (e.g., a list of all municipalities in the country). The list of categories can then be reduced by using the SUBSET feature. When this feature is activated, only categories with a label containing certain characters are displayed. Here is how this works.

First, create a question with an open-end category that collects the few characters to search for. For example, insert a question requesting that the respondent enters a few characters of the name of the municipality where they reside; this question could be named "CHARS_OF_CITYNAME".

Second, create a regular question containing all possible answer categories (like all city names). Add other categories that you want to display all the time (e.g., Other, DK/NR) as required and give them an "F" behaviour (for "force display"). Add the keywork SUBSET= on the question name line of the question definition and complete it with the name of the variable containing the characters to search for, as in SUBSET=CHARS_OF_CITYNAME.

That's it. Upon using the questionnaire, only categories containing the few characters entered in the first question will be displayed when the second question is called up. Of course, these two questions cannot be part of the same screen as the formatting of the second requires that the answer from the first be available. The two questions need not immediately follow each other.

SUBSET type questions otherwise behave exactly like any other question. Therefore, a display condition can be applied to display the SUBSET question only when the initial question requesting a few characters to subset on has been correctly answered (and not left, for example, on a "Don't know").

CODESIN

The CODESIN= parameter of the question name line also performs a selection in the answer categories of the question to which it is attached. In this case, the only categories shown are those whose code is found in the open-end part named after CODESIN=. In the open-end part, the list of codes must be separated with "mu" characters (µ).

For example, the following code extracts part numbers from one data base based on price and displays only those part numbers in the follow-up question:

    PARTNUMBERS CALCUL
    % Text
      APARTNUMBERS = pull_value("UNIQUE","PARTNUMBER","PARTNUMBER_DB","APRICE > 10");
    % Note
    % Categories
     
    % Simple skips
    % Display condition
    % Open parts
      1 = C100
    ! ===================================================
    WHICHPART CODESIN=APARTNUMBERS
    % Text
      Select the appropriate part number
    % Note
    % Categories
    % Simple skips
    % Display condition
    % Open parts
    ! ===================================================

   

Randomly selecting a subset of possible answer categories

Problem

You want to show a random subset of possible answer categories to the respondent.

Solution

Use the select_random_combination function:

    select_random_combination(among,number,exclusive,other_combination,other_combination,...)

where

  • "among" is a positive integer stating the number of available options (e.g., the total number of magazines from which to choose);
  • "number" is a positive integer indicating the size of the subset to select (e.g., 2 to select pairs of options);
  • "exclusive" is 0 (zero) if a given option can be selected in more than one subset, or 1 (one) if it cannot;
  • "other_combination,..." is a series of comma-delimited CallWeb question names (expressed as Perl variables, thus prefixed with a dollar sign) containing the names of all of the questions storing results of random selections of subsets from the same pool; this information is used to avoid picking the same combination twice and to avoid using the same integer twice if the selection is exclusive (see the parameter above).

Discussion

Sometimes, it is necessary to select a random subset of answer categories to be offered to the respondent. An example would be to randomly select two magazines and to ask the respondent to select the one they prefer. The special function select_random_combination is used for this purpose, along with the display of answer choices using category display conditions.

In the context of a CALCUL question, let's analyze the following call to this function:

    Q1 = &select_random_combination(5,2,0,$Q2,$Q3)

This would place, in Q1 (which must have a maximum number of answers greater than 1 to accommodate pairs of selections or trios, etc.), a random selection of two integers between 1 and 5. The random selection will necessarily be different from the selections stored in Q2 and Q3 but it could include an integer already selected in Q2 or Q3.

    Q1 = &select_random_combination(9,3,1,$Q2,$Q3)

This would place, in Q1, a random selection of three integers between 1 and 9. The random selection will necessarily be different from the selections stored in Q2 and Q3 and it cannot include an integer already selected in Q2 or Q3.

It is usually preferable to calculate these random selections only once so that, if the respondent backtracks in the questionnaire, he/she doesn't get assigned a different random selection. The following code achieves this:

    Q1 = $Q1 ? $Q1 : &select_random_combination(9,3,1,$Q2,$Q3)

If it is not possible to find a combination because of the constraints imposed by the programmer, the function returns "---". This would happen:

  • when trying to select more subsets than can be returned by a given situation (e.g., a fourth pair of integers between 1 and 3);
  • in exclusive mode, when trying to select a new subset when other subsets leave fewer integers available than required to build a subset.

The result from this function can be used in another question which will display answer categories based on the calculated question. For example:

    % Answer categories
    [Q1.EQ.1]
    Choice no. 1
    [Q1.EQ.2]
    Choice no. 2
    [Q1.EQ.3]
    Choice no. 3
    [Q1.EQ.4]
    Choice no. 4

will display only the choices that were randomly selected in the Q1 subset.

Randomly selecting a subset of possible answer categories

Problem

You want to show a random subset of possible answer categories to the respondent.

Solution

Use the select_random_combination function:

    select_random_combination(among,number,exclusive,other_combination,other_combination,...)

where

  • "among" is a positive integer stating the number of available options (e.g., the total number of magazines from which to choose);
  • "number" is a positive integer indicating the size of the subset to select (e.g., 2 to select pairs of options);
  • "exclusive" is 0 (zero) if a given option can be selected in more than one subset, or 1 (one) if it cannot;
  • "other_combination,..." is a series of comma-delimited CallWeb question names (expressed as Perl variables, thus prefixed with a dollar sign) containing the names of all of the questions storing results of random selections of subsets from the same pool; this information is used to avoid picking the same combination twice and to avoid using the same integer twice if the selection is exclusive (see the parameter above).

Discussion

Sometimes, it is necessary to select a random subset of answer categories to be offered to the respondent. An example would be to randomly select two magazines and to ask the respondent to select the one they prefer. The special function select_random_combination is used for this purpose, along with the display of answer choices using category display conditions.

In the context of a CALCUL question, let's analyze the following call to this function:

    Q1 = &select_random_combination(5,2,0,$Q2,$Q3)

This would place, in Q1 (which must have a maximum number of answers greater than 1 to accommodate pairs of selections or trios, etc.), a random selection of two integers between 1 and 5. The random selection will necessarily be different from the selections stored in Q2 and Q3 but it could include an integer already selected in Q2 or Q3.

    Q1 = &select_random_combination(9,3,1,$Q2,$Q3)

This would place, in Q1, a random selection of three integers between 1 and 9. The random selection will necessarily be different from the selections stored in Q2 and Q3 and it cannot include an integer already selected in Q2 or Q3.

It is usually preferable to calculate these random selections only once so that, if the respondent backtracks in the questionnaire, he/she doesn't get assigned a different random selection. The following code achieves this:

    Q1 = $Q1 ? $Q1 : &select_random_combination(9,3,1,$Q2,$Q3)

If it is not possible to find a combination because of the constraints imposed by the programmer, the function returns "---". This would happen:

  • when trying to select more subsets than can be returned by a given situation (e.g., a fourth pair of integers between 1 and 3);
  • in exclusive mode, when trying to select a new subset when other subsets leave fewer integers available than required to build a subset.

The result from this function can be used in another question which will display answer categories based on the calculated question. For example:

    % Answer categories
    [Q1.EQ.1]
    Choice no. 1
    [Q1.EQ.2]
    Choice no. 2
    [Q1.EQ.3]
    Choice no. 3
    [Q1.EQ.4]
    Choice no. 4

will display only the choices that were randomly selected in the Q1 subset.

   

Randomly selecting a subset of selected answer categories

Problem

You want to select a random combination of answers provided to a multiple-answer question.

Solution

Use a CALCUL question with an expression like the following:

    RANDOMSELECTION = random_subset($MULTIQ,2)

where

  • "RANDOMSELECTION" is the name of the multiple-selection question (with a MAX= parameter greater than 1) where the random selection will be stored;
  • "MULTIQ" is the name of the multiple-selection question which needs to be subsetted;
  • "2" can be any positive integer and corresponds to the number of answers to select.

Discussion

Sometimes, it is necessary to select a random subset of answers provided to a multiple-selection question. An example would be to randomly select two magazines among a list of magazines selected by the respondent. The special function random_subset is used for this purpose.

This function selects a random subset of the answers provided and returns the result as a value that can be used on another multiple-selection question. If the respondent originally offered fewer answers than required in the random subset, all of their answers will be included in the "random subset".

The result of this function can be used in display conditions as if it had been produced in response to a multiple-selection question.

It is possible to randomly select a single answer as well (e.g., RANDOMSELECTION = random_subset($MULTIQ,1)); such a selection would be stored in a single-answer question, not a multiple-selection question.

Randomly selecting a subset of selected answer categories

Problem

You want to select a random combination of answers provided to a multiple-answer question.

Solution

Use a CALCUL question with an expression like the following:

    RANDOMSELECTION = random_subset($MULTIQ,2)

where

  • "RANDOMSELECTION" is the name of the multiple-selection question (with a MAX= parameter greater than 1) where the random selection will be stored;
  • "MULTIQ" is the name of the multiple-selection question which needs to be subsetted;
  • "2" can be any positive integer and corresponds to the number of answers to select.

Discussion

Sometimes, it is necessary to select a random subset of answers provided to a multiple-selection question. An example would be to randomly select two magazines among a list of magazines selected by the respondent. The special function random_subset is used for this purpose.

This function selects a random subset of the answers provided and returns the result as a value that can be used on another multiple-selection question. If the respondent originally offered fewer answers than required in the random subset, all of their answers will be included in the "random subset".

The result of this function can be used in display conditions as if it had been produced in response to a multiple-selection question.

It is possible to randomly select a single answer as well (e.g., RANDOMSELECTION = random_subset($MULTIQ,1)); such a selection would be stored in a single-answer question, not a multiple-selection question.

   

Adding an initial message to a drop-down list

Problem

You want to create a drop-down list of answer categories and force respondents to select an answer other than the default one.

Solution

Create a non-selectable initial drop-down list category with code the following:

    *1*N*Select a category

Discussion

Usually, the initial category shown by default off a drop-down list is empty or is a message such as "Select a category". This initial category is selected by default in HTML but this is usually not an answer that the questionnaire designer wishes to record (it would be like recording the absence of a selection in a radio group).

The code for the proposed first category creates an initial category labelled "Select a category" and makes it non-selectable — which, in the context of a drop-down list, means that selecting it produces an error message.

Adding an initial message to a drop-down list

Problem

You want to create a drop-down list of answer categories and force respondents to select an answer other than the default one.

Solution

Create a non-selectable initial drop-down list category with code the following:

    *1*N*Select a category

Discussion

Usually, the initial category shown by default off a drop-down list is empty or is a message such as "Select a category". This initial category is selected by default in HTML but this is usually not an answer that the questionnaire designer wishes to record (it would be like recording the absence of a selection in a radio group).

The code for the proposed first category creates an initial category labelled "Select a category" and makes it non-selectable — which, in the context of a drop-down list, means that selecting it produces an error message.

   

Creating a question requesting a value and a unit

Problem

You want to ask a respondent for a number which could be expressed in different units.

Solution

  • create a single-choice question;
  • add the unit categories (such as miles and kilometers);
  • add another category and assign it the open-end part;
  • give that category an N (non-selectable) behaviour code;
  • eventually, add one or more categories with values greater than 900 which could be selected without entering a numeric value in the open-end part (such as Don't know).

Discussion

Questions like this are called "unit-type questions". They have two components: an open-end numeric value and a single-choice series of units. For example, asking for the distance between home and work could be a unit-type question: the open-end numeric value is the number of units of distance and the single-choice series refers to the unit of measurement such as kilometers and miles. From CallWeb's standpoint, what is particular to unit-type questions is that the open-end part should not correspond to a particular unit choice and that the selection of a unit which is not the same code as the open-end must be permissible.

When a code corresponding to an open-end part has an N behaviour code, CallWeb expects that an answer other than that code will be selected by the respondent.

Creating a question requesting a value and a unit

Problem

You want to ask a respondent for a number which could be expressed in different units.

Solution

  • create a single-choice question;
  • add the unit categories (such as miles and kilometers);
  • add another category and assign it the open-end part;
  • give that category an N (non-selectable) behaviour code;
  • eventually, add one or more categories with values greater than 900 which could be selected without entering a numeric value in the open-end part (such as Don't know).

Discussion

Questions like this are called "unit-type questions". They have two components: an open-end numeric value and a single-choice series of units. For example, asking for the distance between home and work could be a unit-type question: the open-end numeric value is the number of units of distance and the single-choice series refers to the unit of measurement such as kilometers and miles. From CallWeb's standpoint, what is particular to unit-type questions is that the open-end part should not correspond to a particular unit choice and that the selection of a unit which is not the same code as the open-end must be permissible.

When a code corresponding to an open-end part has an N behaviour code, CallWeb expects that an answer other than that code will be selected by the respondent.

   

Adding headers in answer sets

Problem

You want to insert headers in an answer set to visually regroup answer categories.

Solution

Insert non-selectable categories in the answer set as in:

    *101*N*EAST
    *1*Newfoundland and Labrador
    *2*Prince Edward Island
    *3*Nova Scotia
    *4*New Brunswick
    *102*N*CENTRAL
    *5*Quebec
    *6*Ontario
    *103*N*WEST
    *7*Manitoba
    *8*Saskatchewan
    *9*Alberta
    *10*British Columbia
    *104*N* 
    *99*Don't know

Discussion

Answer categories tagged with an N behaviour code are considered "non selectable". That means that they are displayed on screen but that no method of selecting them is offered: they don't come with a radio button, nor a check box. Therefore, only their label is displayed and they can play the role of headers in the answer set.

Such headers can be made visually more evident by bolding them, boxing them or changing the color of the text. All of these appearance changes can be implemented through CSS styles and a style can be applied to header text using the SPAN HTML tag as in:

    *101*N*<SPAN CLASS=somestyle>EAST</SPAN>

Adding headers in answer sets

Problem

You want to insert headers in an answer set to visually regroup answer categories.

Solution

Insert non-selectable categories in the answer set as in:

    *101*N*EAST
    *1*Newfoundland and Labrador
    *2*Prince Edward Island
    *3*Nova Scotia
    *4*New Brunswick
    *102*N*CENTRAL
    *5*Quebec
    *6*Ontario
    *103*N*WEST
    *7*Manitoba
    *8*Saskatchewan
    *9*Alberta
    *10*British Columbia
    *104*N*&nbsp;
    *99*Don't know

Discussion

Answer categories tagged with an N behaviour code are considered "non selectable". That means that they are displayed on screen but that no method of selecting them is offered: they don't come with a radio button, nor a check box. Therefore, only their label is displayed and they can play the role of headers in the answer set.

Such headers can be made visually more evident by bolding them, boxing them or changing the color of the text. All of these appearance changes can be implemented through CSS styles and a style can be applied to header text using the SPAN HTML tag as in:

    *101*N*<SPAN CLASS=somestyle>EAST</SPAN>

   

Ensuring that category permutations are the same for two questions

Problem

You want the answer categories of two questions to be presented in random order but in the same order for both questions.

Solution

Use the ROTATION=Qx feature to create parallel permutations.

Discussion

Say, in question Q1, you are asking whether one has read each magazine in a list over the past month. You want that list to be presented in random order to avoid order effects. You can simply add the ROTATION keyword to the question name line.

Let's add a second question Q2 which uses the same list; you want the order of presentation of magazines in Q2 to be the same as in Q1. Then use ROTATION=Q1 on the Q2 question line. This indicates that the answer categories for Q2 should be in the same order as those in Q1.

If Q1 and Q2 are in a question permutation, it is possible that CallWeb would display Q2 before Q1. In such circumstance, there would be no random order to apply to the answer categories in Q2 and CallWeb would generate an answer category permutation for Q2 independently from Q1. In this example, to make sure that Q1 and Q2 use parallel permutation, use ROTATION=Q2 for Q1 and ROTATION=Q1 for Q2. The first question displayed will control the persentation of the answer categories of the second one.

This recipe applies to two questions. If more than two questions must share a random order of answer caregories, make sure that one of them is always displayed first and make the other ones dependent upon that first question.

Ensuring that category permutations are the same for two questions

Problem

You want the answer categories of two questions to be presented in random order but in the same order for both questions.

Solution

Use the ROTATION=Qx feature to create parallel permutations.

Discussion

Say, in question Q1, you are asking whether one has read each magazine in a list over the past month. You want that list to be presented in random order to avoid order effects. You can simply add the ROTATION keyword to the question name line.

Let's add a second question Q2 which uses the same list; you want the order of presentation of magazines in Q2 to be the same as in Q1. Then use ROTATION=Q1 on the Q2 question line. This indicates that the answer categories for Q2 should be in the same order as those in Q1.

If Q1 and Q2 are in a question permutation, it is possible that CallWeb would display Q2 before Q1. In such circumstance, there would be no random order to apply to the answer categories in Q2 and CallWeb would generate an answer category permutation for Q2 independently from Q1. In this example, to make sure that Q1 and Q2 use parallel permutation, use ROTATION=Q2 for Q1 and ROTATION=Q1 for Q2. The first question displayed will control the persentation of the answer categories of the second one.

This recipe applies to two questions. If more than two questions must share a random order of answer caregories, make sure that one of them is always displayed first and make the other ones dependent upon that first question.

   

Drawing answer categories from an external source

Problem

You want to ask for a selection among choices that are not predetermined but rather extracted from an external source.

Solution

Use the CATEGORIES keywork on the question name line.

Discussion

Sometimes, the categories that you want the respondent to choose from are not predetermined. For example, you could want a schoolboard to select from a list of schools that is extracted from a data base maintained outside of CallWeb. While there are other strategies available if the external list is short, a long external list is best handled with the CATEGORIES question type.

The CATEGORIES question type identifies the name of an open-end part which contains the categories to be displayed. That open-end part must contain a string of pairs of codes and labels all delimited with a mu character ($dlm_niveau1 if used in a calculation). Here is an example of such a pair of variables.

    SCHOOL_LIST CALCUL
    % question
      $ASCHOOL_LIST =
        "CODE1$dlm_niveau1Label 1$dlm_niveau1"
        . "CODE2$dlm_niveau1Label 2$dlm_niveau1"
        . "CODE3$dlm_niveau1Label 3$dlm_niveau1"
        . "CODE4$dlm_niveau1Label 4$dlm_niveau1"
        . "CODE5$dlm_niveau1Label 5$dlm_niveau1"
        . "CODE6$dlm_niveau1Label 6$dlm_niveau1"
        . "CODE7$dlm_niveau1Label 7$dlm_niveau1"
        . "CODE8$dlm_niveau1Label 8$dlm_niveau1"
        . "CODE9$dlm_niveau1Label 9$dlm_niveau1"
        . "CODE10$dlm_niveau1Label 10"
    % note
    % categories

    % simple skips
    % condition
    % open
      1 = C10 1 10
    ! ==================================================

    DELEGATIONS MIN=0 MAX=1 CATEGORIES=ASCHOOL_LIST
    % question
      Please make a selection.
    % note
    % categories
      *9999*I*[EN]Open-end answer to support the open-end part. Made invisible to the respondent with the I behaviour code.
    % simple skips
    % condition
    % open
      9999 = C10 1 10
    ! ==================================================

ASCHOOL_LIST contains a mu-delimited list of school codes and school labels. In this example, the list is created by a direct CALCULation but it could be created from an extraction from another data base that is post-formatted as a mu-delimited list. ASCHOOL_LIST is then used as the argument to the CATEGORIES options of DELEGATIONS.

DELEGATIONS is a CATEGORIES question using ASCHOOL_LIST as input. This means that the options "Label 1" to "Label 10" are displayed in DELEGATIONS (as implicit answer codes 1 to 10 because the numbering of answers starts at 1 and goes up to the number of external categories). By default, each category label is displayed with its category code appended, in parentheses; it is possible to avoid appending the category code by adding a tiles (~) immediately after the equal sign (as in CATEGORIES=~ASCHOOL_LIST). A CATEGORIES question needs an open-ended part to store the selected codes (9999 in the example); the open-ended part code should have a behaviour code of "I" to make it invisible to the respondent. The selected of external codes are stored in the open-ended part of the CATEGORIES question, delimited by mu characters.

Drawing answer categories from an external source

Problem

You want to ask for a selection among choices that are not predetermined but rather extracted from an external source.

Solution

Use the CATEGORIES keywork on the question name line.

Discussion

Sometimes, the categories that you want the respondent to choose from are not predetermined. For example, you could want a schoolboard to select from a list of schools that is extracted from a data base maintained outside of CallWeb. While there are other strategies available if the external list is short, a long external list is best handled with the CATEGORIES question type.

The CATEGORIES question type identifies the name of an open-end part which contains the categories to be displayed. That open-end part must contain a string of pairs of codes and labels all delimited with a mu character ($dlm_niveau1 if used in a calculation). Here is an example of such a pair of variables.

    SCHOOL_LIST CALCUL
    % question
      $ASCHOOL_LIST =
        "CODE1$dlm_niveau1Label 1$dlm_niveau1"
        . "CODE2$dlm_niveau1Label 2$dlm_niveau1"
        . "CODE3$dlm_niveau1Label 3$dlm_niveau1"
        . "CODE4$dlm_niveau1Label 4$dlm_niveau1"
        . "CODE5$dlm_niveau1Label 5$dlm_niveau1"
        . "CODE6$dlm_niveau1Label 6$dlm_niveau1"
        . "CODE7$dlm_niveau1Label 7$dlm_niveau1"
        . "CODE8$dlm_niveau1Label 8$dlm_niveau1"
        . "CODE9$dlm_niveau1Label 9$dlm_niveau1"
        . "CODE10$dlm_niveau1Label 10"
    % note
    % categories

    % simple skips
    % condition
    % open
      1 = C10 1 10
    ! ==================================================

    DELEGATIONS MIN=0 MAX=1 CATEGORIES=ASCHOOL_LIST
    % question
      Please make a selection.
    % note
    % categories
      *9999*I*[EN]Open-end answer to support the open-end part. Made invisible to the respondent with the I behaviour code.
    % simple skips
    % condition
    % open
      9999 = C10 1 10
    ! ==================================================

ASCHOOL_LIST contains a mu-delimited list of school codes and school labels. In this example, the list is created by a direct CALCULation but it could be created from an extraction from another data base that is post-formatted as a mu-delimited list. ASCHOOL_LIST is then used as the argument to the CATEGORIES options of DELEGATIONS.

DELEGATIONS is a CATEGORIES question using ASCHOOL_LIST as input. This means that the options "Label 1" to "Label 10" are displayed in DELEGATIONS (as implicit answer codes 1 to 10 because the numbering of answers starts at 1 and goes up to the number of external categories). By default, each category label is displayed with its category code appended, in parentheses; it is possible to avoid appending the category code by adding a tiles (~) immediately after the equal sign (as in CATEGORIES=~ASCHOOL_LIST). A CATEGORIES question needs an open-ended part to store the selected codes (9999 in the example); the open-ended part code should have a behaviour code of "I" to make it invisible to the respondent. The selected of external codes are stored in the open-ended part of the CATEGORIES question, delimited by mu characters.

   

Keeping the table columns the same width

Problem

You want to ensure that all columns in a table are the same width.

Solution

Use the Pixels option of the TABLE pound instruction.

Discussion

When questions and answers are presented in matrix format (with questions as rows and answers as columns, usually), it is visually pleasing that all answer columns be the same width. In the case of columns housing a scale (such as a satisfaction or an agreement scale), keeping columns all the same width reinforces the interval-scale nature of the set of responses.

The TABLE pound instruction possesses an option to specify the minimum number of pixels to be used by each column (other than the first, row label column). In the following example,

    # Table 1 = Q1-Q4 PIXELS=80

the "80" indicates that each column should use at least 80 pixels.

The Pixels value is the minimum column width. If one word in the header cell is larger than that value, the column accommodates that larger width. In order to ensure that the column uses exactly the number of pixels stated by the Pixels option, hyphenate long words by adding a hyphen followed by a <br> (line break).

Keeping the table columns the same width

Problem

You want to ensure that all columns in a table are the same width.

Solution

Use the Pixels option of the TABLE pound instruction.

Discussion

When questions and answers are presented in matrix format (with questions as rows and answers as columns, usually), it is visually pleasing that all answer columns be the same width. In the case of columns housing a scale (such as a satisfaction or an agreement scale), keeping columns all the same width reinforces the interval-scale nature of the set of responses.

The TABLE pound instruction possesses an option to specify the minimum number of pixels to be used by each column (other than the first, row label column). In the following example,

    # Table 1 = Q1-Q4 PIXELS=80

the "80" indicates that each column should use at least 80 pixels.

The Pixels value is the minimum column width. If one word in the header cell is larger than that value, the column accommodates that larger width. In order to ensure that the column uses exactly the number of pixels stated by the Pixels option, hyphenate long words by adding a hyphen followed by a <br> (line break).

   

Inserting an other box in a table

Problem

You want to lay out several items in a table format and add an "other" choice at the end.

Solution

  1. create a STOCK-type question
  2. give that STOCK question a single answer code that corresponds to an open-end part; give the open-end part the characteristics required by the application
  3. place that STOCK question outside of the table page (as STOCK questions are not allowed within pages)
  4. in the note field of the "Other" item of the table, add the following to the text of the note (which will become the text of the line label in the table): <BOX>name_of_the_STOCK_question</BOX>; do the same in all language versions of the item text
  5. optionally, add MUST conditions to the STOCK question and/or to the associated regular question(s) to ensure that a label is entered when answers are provided and vice versa.

Discussion

Lists presented in table format sometimes end with an "Other, specify" item, as in the following incomplete example of a data table:

...
Feature 8
Feature 9
Other

The following code could produce this output. The MUST parameters ensure that an answer is required in both questions if one is given in either question.

    FEATURE
    % question
    % note
       [SUFFIX:8]Feature 8
       [SUFFIX:9]Feature 9
    % categories
       *1*
       *2*
    % skip
    % condition
    % open
    ! ==============================================================
    OTHER MIN=0 MAX=1 MUST=(AOTHERBOX)
    % question
    % note
       Other <BOX>AOTHERBOX</BOX>
    % categories
       *1*
       *2*
    % skip
    % condition
    % open
    ! ==============================================================
    OTHERBOX MIN=0 MAX=1 STOCK MUST=(OTHER.EQ.1-2)
    % question
    % note
    % categories

    % skip
    % condition
    % open
       1 = C50 1 15
    ! ==============================================================

Note that <BOX></BOX> insertions can be put anywhere where text is expected (e.g., in answer categories, in notes, even in question text).

Inserting an other box in a table

Problem

You want to lay out several items in a table format and add an "other" choice at the end.

Solution

  1. create a STOCK-type question
  2. give that STOCK question a single answer code that corresponds to an open-end part; give the open-end part the characteristics required by the application
  3. place that STOCK question outside of the table page (as STOCK questions are not allowed within pages)
  4. in the note field of the "Other" item of the table, add the following to the text of the note (which will become the text of the line label in the table): <BOX>name_of_the_STOCK_question</BOX>; do the same in all language versions of the item text
  5. optionally, add MUST conditions to the STOCK question and/or to the associated regular question(s) to ensure that a label is entered when answers are provided and vice versa.

Discussion

Lists presented in table format sometimes end with an "Other, specify" item, as in the following incomplete example of a data table:

...
Feature 8
Feature 9
Other

The following code could produce this output. The MUST parameters ensure that an answer is required in both questions if one is given in either question.

    FEATURE
    % question
    % note
       [SUFFIX:8]Feature 8
       [SUFFIX:9]Feature 9
    % categories
       *1*
       *2*
    % skip
    % condition
    % open
    ! ==============================================================
    OTHER MIN=0 MAX=1 MUST=(AOTHERBOX)
    % question
    % note
       Other <BOX>AOTHERBOX</BOX>
    % categories
       *1*
       *2*
    % skip
    % condition
    % open
    ! ==============================================================
    OTHERBOX MIN=0 MAX=1 STOCK MUST=(OTHER.EQ.1-2)
    % question
    % note
    % categories

    % skip
    % condition
    % open
       1 = C50 1 15
    ! ==============================================================

Note that <BOX></BOX> insertions can be put anywhere where text is expected (e.g., in answer categories, in notes, even in question text).

   

Displaying semantic differential scales

Problem

You want to lay out items in a semantic differential format.

Solution

This table...

 1234567 
repulsiveattractive
old-fashionmodern
cheapexpensive

...is produced with the following code:

    # ECRAN Q1 = Q1A - Q1C
    # TABLE Q1 = Q1A - Q1C
    Q1A
    % question
    % note
       repulsive
    % categories
       1
       2
       3
       4
       5
       6
       7
       *97*NM*<SPAN CLASS=LIGNE>attractive</SPAN>
    % skips
    % condition
    % open
    !
    Q1B
    % question
    % note
       old-fashion
    % categories
       1
       2
       3
       4
       5
       6
       7
       *97*NM*<SPAN CLASS=LIGNE>modern</SPAN>
    % skips
    % condition
    % open
    !
    Q1C
    % question
    % note
       cheap
    % categories
       1
       2
       3
       4
       5
       6
       7
       *97*NM*<SPAN CLASS=LIGNE>expensive</SPAN>
    % skips
    % condition
    % open
    !

Discussion

A battery of semantic differential scales typically focus on one object and tests different attributes of that object using scales that different end-points, such as repulsive vs. attractive, old-fashion vs. modern, cheap vs. expensive.

How does the CallWeb code works?

  • the text in the Note segment is placed in the left cell of each row of the table;
  • the last category (97, in the example) is given an "N" behaviour code which makes it non selectable; in the context of a single choice question in a table, that behaviour code eliminates the radio button from the cell;
  • the same last category is given an "M" behaviour code which places the text of the answer category right in the row cell instead of in the header cell of the column;
  • the LIGNE style is applied to the text of the category so that it is formatted like the question note located on the left side of the table.

A similar technique can be used to create scales that are recurringly labelled at both ends as in:

  1234567 
I like my jobTotally disagreeTotally agree
My supervisor does a good jobTotally disagreeTotally agree
I get compensated for overtimeTotally disagreeTotally agree

This table uses the following code:

    # ECRAN Q1 = Q1A - Q1C
    # TABLE Q1 = Q1A - Q1C
    Q1
    % question
    % note
       [SUFFIX:A]I like my job
       [SUFFIX:B]My supervisor does a good job
       [SUFFIX:C]I get compensated for overtime
    % categories
       *901*NM*Totally disagree
       *1*1
       *2*2
       *3*3
       *4*4
       *5*5
       *6*6
       *7*7
       *902*NM*Totally agree
    % skips
    % condition
    % open
    !

Displaying semantic differential scales

Problem

You want to lay out items in a semantic differential format.

Solution

This table...

 1234567 
repulsiveattractive
old-fashionmodern
cheapexpensive

...is produced with the following code:

    # ECRAN Q1 = Q1A - Q1C
    # TABLE Q1 = Q1A - Q1C
    Q1A
    % question
    % note
       repulsive
    % categories
       1
       2
       3
       4
       5
       6
       7
       *97*NM*<SPAN CLASS=LIGNE>attractive</SPAN>
    % skips
    % condition
    % open
    !
    Q1B
    % question
    % note
       old-fashion
    % categories
       1
       2
       3
       4
       5
       6
       7
       *97*NM*<SPAN CLASS=LIGNE>modern</SPAN>
    % skips
    % condition
    % open
    !
    Q1C
    % question
    % note
       cheap
    % categories
       1
       2
       3
       4
       5
       6
       7
       *97*NM*<SPAN CLASS=LIGNE>expensive</SPAN>
    % skips
    % condition
    % open
    !

Discussion

A battery of semantic differential scales typically focus on one object and tests different attributes of that object using scales that different end-points, such as repulsive vs. attractive, old-fashion vs. modern, cheap vs. expensive.

How does the CallWeb code works?

  • the text in the Note segment is placed in the left cell of each row of the table;
  • the last category (97, in the example) is given an "N" behaviour code which makes it non selectable; in the context of a single choice question in a table, that behaviour code eliminates the radio button from the cell;
  • the same last category is given an "M" behaviour code which places the text of the answer category right in the row cell instead of in the header cell of the column;
  • the LIGNE style is applied to the text of the category so that it is formatted like the question note located on the left side of the table.

A similar technique can be used to create scales that are recurringly labelled at both ends as in:

  1234567 
I like my jobTotally disagreeTotally agree
My supervisor does a good jobTotally disagreeTotally agree
I get compensated for overtimeTotally disagreeTotally agree

This table uses the following code:

    # ECRAN Q1 = Q1A - Q1C
    # TABLE Q1 = Q1A - Q1C
    Q1
    % question
    % note
       [SUFFIX:A]I like my job
       [SUFFIX:B]My supervisor does a good job
       [SUFFIX:C]I get compensated for overtime
    % categories
       *901*NM*Totally disagree
       *1*1
       *2*2
       *3*3
       *4*4
       *5*5
       *6*6
       *7*7
       *902*NM*Totally agree
    % skips
    % condition
    % open
    !

   

Using the top left corner of the table

Problem

You want to add information (text or otherwise) in the top left corner of a questionnaire table.

Solution

Use the CORNER (or COIN) parameter of the TABLE pound instruction.

Discussion

A typical questionnaire table layout defined by this TABLE pound instruction looks as follows.

    # TABLE Features = F1-F3
YesNoDon't know
Feature 1
Feature 2
Feature 3

Note that the top left corner of this table is left unused. To add information (typically text) in this table box, use the CORNER parameter of the TABLE pound instruction, as in:

    # TABLE Features = F1-F3 CORNER=(List of features)
List of featuresYesNoDon't know
Feature 1
Feature 2
Feature 3

The CORNER text (a.k.a. COIN) needs to be surrounded by parentheses so that CallWeb knows where the CORNER information starts and finishes. If you need to include parentheses in the text, use the HTML special codes &#40; for an open parenthesis and &#41; for a closed parenthesis. Different language segments can also be provided using the usual "bracket-language code-bracket" syntax as in:

    # TABLE Features = F1-F3 CORNER=([EN]List of features[FR]Liste de caractéristiques)

It is also possible to change the corner information when the header line is repeated within a table. For example:

First list of featuresYesNoDon't know
Feature 1
Feature 2
Second list of featuresYesNoDon't know
Feature 3

This is requires two steps:

  • First, you must trigger the display of the second header. This is accomplished either by adding a header repetition factor (ENTETE=n) on the TABLE pound instruction:
    # TABLE Features = F1-F3 HEADER=2
    or by adding a FORCEHEADER keyword on the question name line of the question which comes after the new header:
    F3 FORCEHEADER
  • Second, you must change the CORNER text for the question which triggers the new header. This can be done by adding a CORNER parameter on the question name line:
    F3 CORNER=(Second list of features)
    or on the SUFFIX line:
    [SUFFIX:3][CORNER=(Second list of features)]Feature 3

The highest priority in selecting the CORNER text is given to SUFFIX instructions, then to the question name line, and finally to the TABLE pound instruction.

Using the top left corner of the table

Problem

You want to add information (text or otherwise) in the top left corner of a questionnaire table.

Solution

Use the CORNER (or COIN) parameter of the TABLE pound instruction.

Discussion

A typical questionnaire table layout defined by this TABLE pound instruction looks as follows.

    # TABLE Features = F1-F3
YesNoDon't know
Feature 1
Feature 2
Feature 3

Note that the top left corner of this table is left unused. To add information (typically text) in this table box, use the CORNER parameter of the TABLE pound instruction, as in:

    # TABLE Features = F1-F3 CORNER=(List of features)
List of featuresYesNoDon't know
Feature 1
Feature 2
Feature 3

The CORNER text (a.k.a. COIN) needs to be surrounded by parentheses so that CallWeb knows where the CORNER information starts and finishes. If you need to include parentheses in the text, use the HTML special codes &#40; for an open parenthesis and &#41; for a closed parenthesis. Different language segments can also be provided using the usual "bracket-language code-bracket" syntax as in:

    # TABLE Features = F1-F3 CORNER=([EN]List of features[FR]Liste de caractéristiques)

It is also possible to change the corner information when the header line is repeated within a table. For example:

First list of featuresYesNoDon't know
Feature 1
Feature 2
Second list of featuresYesNoDon't know
Feature 3

This is requires two steps:

  • First, you must trigger the display of the second header. This is accomplished either by adding a header repetition factor (ENTETE=n) on the TABLE pound instruction:
    # TABLE Features = F1-F3 HEADER=2
    or by adding a FORCEHEADER keyword on the question name line of the question which comes after the new header:
    F3 FORCEHEADER
  • Second, you must change the CORNER text for the question which triggers the new header. This can be done by adding a CORNER parameter on the question name line:
    F3 CORNER=(Second list of features)
    or on the SUFFIX line:
    [SUFFIX:3][CORNER=(Second list of features)]Feature 3

The highest priority in selecting the CORNER text is given to SUFFIX instructions, then to the question name line, and finally to the TABLE pound instruction.

   

Modifying the appearance of recalled text

Problem

You want to modify the way recalled text is displayed on screen.

Solution

Use the SUBSTITUT CSS style or create a new CSS and tag the recalled text using that style.

Discussion

Text that is recalled using the &QUESTIONNAME syntax or the &&AQUESTIONNAME syntax is automatically tagged with the SUBSTITUT CSS style. You can change the appearance of the recalled text by adjusting this style in the project styles.css file.

You can also implement finer adjustments by tagging the recall using a custom style as in:

    <SPAN CLASS=somestyle>&QUESTIONNAME</SPAN>

and defining that new style in the styles.css file. For example, the following style definition displays the recalled text entirely in lowercase:

    .somestyle {text-transform: lowercase}

Modifying the appearance of recalled text

Problem

You want to modify the way recalled text is displayed on screen.

Solution

Use the SUBSTITUT CSS style or create a new CSS and tag the recalled text using that style.

Discussion

Text that is recalled using the &QUESTIONNAME syntax or the &&AQUESTIONNAME syntax is automatically tagged with the SUBSTITUT CSS style. You can change the appearance of the recalled text by adjusting this style in the project styles.css file.

You can also implement finer adjustments by tagging the recall using a custom style as in:

    <SPAN CLASS=somestyle>&QUESTIONNAME</SPAN>

and defining that new style in the styles.css file. For example, the following style definition displays the recalled text entirely in lowercase:

    .somestyle {text-transform: lowercase}

   

Displaying different text upon recall of an answer

Problem

You want to recall a previous response but display text that is different from the original answer.

Solution

Use the ALIAS feature of answer categories and a recall which uses the ALIAS number.

Discussion

It is sometimes useful to recall a label other than the one displayed originally when asking the question. For example, say you ask the following question:

    Would you qualify this event as
      an accident
      an incident
      Don't know

and you want to the follow-up question to read Was this accident... in the first case, Was this incident... in the second case and Was this event... in the third case.

You could create a second CALCUL question to compute a synonym and recall these labels or you could use the EXECUTE syntax and the Perl ternary operator to display a label conditional to the previous answer. But there is a simpler solution: label aliases.

In the example above, one would code the intial question as follows:

    Q1
    %
    Would you qualify this event as
    %
    an accident[ALIAS1:accident]
    an incident[ALIAS1:incident]
    Don't know[ALIAS1:event]

and use the following recall in the subsequent question:

    Was this &Q1#1

The &Q1 recalls the answers from Q1 and the #1 states that ALIAS1 must be used. If one category did not possess an ALIAS1, the standard label would be displayed. There can be more than one alias on any given code; the one corresponding to the recall number is displayed.

Displaying different text upon recall of an answer

Problem

You want to recall a previous response but display text that is different from the original answer.

Solution

Use the ALIAS feature of answer categories and a recall which uses the ALIAS number.

Discussion

It is sometimes useful to recall a label other than the one displayed originally when asking the question. For example, say you ask the following question:

    Would you qualify this event as
      an accident
      an incident
      Don't know

and you want to the follow-up question to read Was this accident... in the first case, Was this incident... in the second case and Was this event... in the third case.

You could create a second CALCUL question to compute a synonym and recall these labels or you could use the EXECUTE syntax and the Perl ternary operator to display a label conditional to the previous answer. But there is a simpler solution: label aliases.

In the example above, one would code the intial question as follows:

    Q1
    %
    Would you qualify this event as
    %
    an accident[ALIAS1:accident]
    an incident[ALIAS1:incident]
    Don't know[ALIAS1:event]

and use the following recall in the subsequent question:

    Was this &Q1#1

The &Q1 recalls the answers from Q1 and the #1 states that ALIAS1 must be used. If one category did not possess an ALIAS1, the standard label would be displayed. There can be more than one alias on any given code; the one corresponding to the recall number is displayed.

   

Controlling where to show the progress bar

Problem

You want to control where the progress bar is shown on screen.

Solution

Use the appropriate pound instructions.

Discussion

Three pound instructions affect the placement of the progress bar (also called thermometer) on the questionnaire page.

# Display thermometer determines whether the progress bar is displayed at all. Its default value is NONE which means that the progress bar is not displayed.

Other possible values of # Display thermometer activate the display of the progress bar and determine whether it is presented at the TOP (alternatively HAUT) or at the BOTTOM (alternatively, BAS) of the questionnaire. The same pound insrtuction identifies whether the progress bar is expressed as a percentage of the questionnaire that is completed or as a number of pages left in the questionnaire; the former corresponds to the keyword PERCENT (alternatively, POURCENTAGE) and the latter to NUMBER (alternatively, NOMBRE).

Thus, the following instruction displays the progress bar at the top of the questionnaire, as the percentage of the questionnaire that is completed:

    # Display thermometer = TOP, PERCENT

The following instruction displays the progress bar at the bottom of the questionnaire, as the number of pages left in the questionnaire:

    # Display thermometer = BOTTOM, NUMBER
The other two pound instructions involved in the placement of the progress bar are # Button order top and # Button order bottom. They control the position of the progress bar among the other buttons like the Previous Page button, the Next Page button and the Language Switch buttons. In both cases, the order of the objects is determined by the order of key letters:
  • [B]ack button
  • [N]ext button
  • [U]nlock button
  • [L]anguage buttons
  • [T]hermometer
  • [S]top button

Thus, this instruction places the progress bar between the Previous Page and Next Page buttons:

    # Button order bottom = BTN

In template mode, the placement of the progress bar is also affected by # Display thermometer but the instructions &*BUTTONSTOP and &*BUTTONBOTTOM place the "top" buttons (defined by # Button order top) and the "bottom" buttons (defined by # Button order bottom) anywhere on the page. Additionally, the instructions &*BUTTONH and &*BUTTONV, using the same series of key letters listed above, display a horizontal or a vertical list of buttons anywhere on the page.

Controlling where to show the progress bar

Problem

You want to control where the progress bar is shown on screen.

Solution

Use the appropriate pound instructions.

Discussion

Three pound instructions affect the placement of the progress bar (also called thermometer) on the questionnaire page.

# Display thermometer determines whether the progress bar is displayed at all. Its default value is NONE which means that the progress bar is not displayed.

Other possible values of # Display thermometer activate the display of the progress bar and determine whether it is presented at the TOP (alternatively HAUT) or at the BOTTOM (alternatively, BAS) of the questionnaire. The same pound insrtuction identifies whether the progress bar is expressed as a percentage of the questionnaire that is completed or as a number of pages left in the questionnaire; the former corresponds to the keyword PERCENT (alternatively, POURCENTAGE) and the latter to NUMBER (alternatively, NOMBRE).

Thus, the following instruction displays the progress bar at the top of the questionnaire, as the percentage of the questionnaire that is completed:

    # Display thermometer = TOP, PERCENT

The following instruction displays the progress bar at the bottom of the questionnaire, as the number of pages left in the questionnaire:

    # Display thermometer = BOTTOM, NUMBER
The other two pound instructions involved in the placement of the progress bar are # Button order top and # Button order bottom. They control the position of the progress bar among the other buttons like the Previous Page button, the Next Page button and the Language Switch buttons. In both cases, the order of the objects is determined by the order of key letters:
  • [B]ack button
  • [N]ext button
  • [U]nlock button
  • [L]anguage buttons
  • [T]hermometer
  • [S]top button

Thus, this instruction places the progress bar between the Previous Page and Next Page buttons:

    # Button order bottom = BTN

In template mode, the placement of the progress bar is also affected by # Display thermometer but the instructions &*BUTTONSTOP and &*BUTTONBOTTOM place the "top" buttons (defined by # Button order top) and the "bottom" buttons (defined by # Button order bottom) anywhere on the page. Additionally, the instructions &*BUTTONH and &*BUTTONV, using the same series of key letters listed above, display a horizontal or a vertical list of buttons anywhere on the page.

   

Formatting the progress bar

Problem

You want to control the appearence of the progress bar.

Solution

Use the appropriate pound instructions.

Discussion

No less than nine pound instructions control the appearence of the progress bar. The easiest way to identify them is using an image like this one.

Formatting the progress bar

Problem

You want to control the appearence of the progress bar.

Solution

Use the appropriate pound instructions.

Discussion

No less than nine pound instructions control the appearence of the progress bar. The easiest way to identify them is using an image like this one.

   

Formatting buttons, boxes and dropdown lists

Problem

You want to control the appearence of buttons, boxes and dropdown lists.

Solution

Use the .BUTTON, .TEXTBOX and .DROPDOWN CSS styles in the style.css file located in the project directory.

Discussion

Questionnaire HTML buttons are formatted according to the .BUTTON CSS style whereas questionnaire open-end text use the .TEXTBOX CSS style, numeric boxes use .NUMBOX, and the dropdown lists follow the .DROPDOWN CSS style. All CSS properties relevant to these HTML objects can be adjusted.

For example, here is an image of a typical (unformatted) HTML button: Let's create the following CSS .BUTTON style and store it in the style.css file for the project:

    .BUTTON { padding: 3px; color: #224059; background-color: #b5c9e2; border: 1px #000000 solid; }

Here is how the button now displays: (Cute!) Buttons can also benefit from a visual effect when the mouse travels over them. Let's redefine the .BUTTON style and add a .BUTTON:hover style as follows. (We can't show the result here but you can try it in your projects.)

    .BUTTON, .BUTTON:hover { font-weight: bold; padding: 3px; color: #224059; background-color: #b5c9e2; border: 1px #000000 solid; }
    .BUTTON:hover { border: 2px #000000 solid; padding: 2px; }

The same is true for text boxes. Here is an unformatted text box:

Let's apply the following CSS .TEXTBOX style and store it in the style.css file for the project:

    .TEXTBOX { padding: 3px; background-color: #E4EDF9; border: 1px #000000 solid; }

Here is how the text box now displays:

Numeric boxes are controlled by the .NUMBOX format. Distinguishing numeric boxes from text boxes allows things like the right-justification of numbers in numeric boxes (using text-align: right;).

Let's see dropdown lists. Here is an unformatted dropdown list:

Let's apply the following CSS .DROPDOWN style and store it in the style.css file for the project:

    .DROPDOWN { background-color: #E4EDF9; }

Here is how the droprown now displays:

CATI CONTEXT: In the CATI interface, buttons are used for a variety of purposes. CallWeb offers a collection of CSS styles that can produce more professional-looking pages. The :hover trick can be used to "animate" the buttons.

Formatting buttons, boxes and dropdown lists

Problem

You want to control the appearence of buttons, boxes and dropdown lists.

Solution

Use the .BUTTON, .TEXTBOX and .DROPDOWN CSS styles in the style.css file located in the project directory.

Discussion

Questionnaire HTML buttons are formatted according to the .BUTTON CSS style whereas questionnaire open-end text use the .TEXTBOX CSS style, numeric boxes use .NUMBOX, and the dropdown lists follow the .DROPDOWN CSS style. All CSS properties relevant to these HTML objects can be adjusted.

For example, here is an image of a typical (unformatted) HTML button: Let's create the following CSS .BUTTON style and store it in the style.css file for the project:

    .BUTTON { padding: 3px; color: #224059; background-color: #b5c9e2; border: 1px #000000 solid; }

Here is how the button now displays: (Cute!) Buttons can also benefit from a visual effect when the mouse travels over them. Let's redefine the .BUTTON style and add a .BUTTON:hover style as follows. (We can't show the result here but you can try it in your projects.)

    .BUTTON, .BUTTON:hover { font-weight: bold; padding: 3px; color: #224059; background-color: #b5c9e2; border: 1px #000000 solid; }
    .BUTTON:hover { border: 2px #000000 solid; padding: 2px; }

The same is true for text boxes. Here is an unformatted text box:

Let's apply the following CSS .TEXTBOX style and store it in the style.css file for the project:

    .TEXTBOX { padding: 3px; background-color: #E4EDF9; border: 1px #000000 solid; }

Here is how the text box now displays:

Numeric boxes are controlled by the .NUMBOX format. Distinguishing numeric boxes from text boxes allows things like the right-justification of numbers in numeric boxes (using text-align: right;).

Let's see dropdown lists. Here is an unformatted dropdown list:

Let's apply the following CSS .DROPDOWN style and store it in the style.css file for the project:

    .DROPDOWN { background-color: #E4EDF9; }

Here is how the droprown now displays:

CATI CONTEXT: In the CATI interface, buttons are used for a variety of purposes. CallWeb offers a collection of CSS styles that can produce more professional-looking pages. The :hover trick can be used to "animate" the buttons.

   

Adaptative button text

Problem

You want the text on a button to change according to circumstances, such as at the end of sections in a questionnaire.

Solution

Use conditionnally recalled values in the pound instructions that define the text on buttons.

Discussion

First, let's clarify which pound instructions control the text of the buttons (note that the same trick could be used to change the image used by image-based buttons):

  • # Forward text
  • # Submit text
  • # Backwards text
  • # Unlock text
  • # Stop text

Let's focus on the "Next page" button (# Forward text) — the discussion applies to the other instructions.

The common use of this instruction is as follows:

    # Forward text = [EN]Next page >>>[FR]Page suivante >>>

This will show the text "Next page >>>" on the submit button on every page that is not CULDESAC or SUBMIT.

Let's separate the text of the button from the # Forward text instruction. The following # Recall instruction creates a &#NEXTBUTTON recall that outputs the button text:

    # RECALL NEXTBUTTON = [EN]Next page >>>[FR]Page suivante >>>
    # Forward text = [EN]&#NEXTBUTTON[FR]&#NEXTBUTTON

Now, let's say that the questionnaire designer would prefer to have a button that says "Submit section 1" and the end of section 1. They could use the following instructions:

    # RECALL NEXTBUTTON =
       #> [QUESTION(ENDS1)][EN]Submit Section 1[FR]Envoyer la section 1
       #> [ELSE][EN]Next page >>>[FR]Page suivante >>>
    # Forward text = [EN]&#NEXTBUTTON[FR]&#NEXTBUTTON

This solution assumes that ENDS1 is the question displayed at the end of Section 1. Any other condition could be used. Several conditions could be created as well as in this:

    # RECALL NEXTBUTTON =
       #> [QUESTION(ENDS1)][EN]Submit Section 1[FR]Envoyer la section 1
       #> [QUESTION(ENDS2)][EN]Submit Section 2[FR]Envoyer la section 2
       #> [ELSE][EN]Next page >>>[FR]Page suivante >>>
    # Forward text = [EN]&#NEXTBUTTON[FR]&#NEXTBUTTON

Adaptative button text

Problem

You want the text on a button to change according to circumstances, such as at the end of sections in a questionnaire.

Solution

Use conditionnally recalled values in the pound instructions that define the text on buttons.

Discussion

First, let's clarify which pound instructions control the text of the buttons (note that the same trick could be used to change the image used by image-based buttons):

  • # Forward text
  • # Submit text
  • # Backwards text
  • # Unlock text
  • # Stop text

Let's focus on the "Next page" button (# Forward text) — the discussion applies to the other instructions.

The common use of this instruction is as follows:

    # Forward text = [EN]Next page >>>[FR]Page suivante >>>

This will show the text "Next page >>>" on the submit button on every page that is not CULDESAC or SUBMIT.

Let's separate the text of the button from the # Forward text instruction. The following # Recall instruction creates a &#NEXTBUTTON recall that outputs the button text:

    # RECALL NEXTBUTTON = [EN]Next page >>>[FR]Page suivante >>>
    # Forward text = [EN]&#NEXTBUTTON[FR]&#NEXTBUTTON

Now, let's say that the questionnaire designer would prefer to have a button that says "Submit section 1" and the end of section 1. They could use the following instructions:

    # RECALL NEXTBUTTON =
       #> [QUESTION(ENDS1)][EN]Submit Section 1[FR]Envoyer la section 1
       #> [ELSE][EN]Next page >>>[FR]Page suivante >>>
    # Forward text = [EN]&#NEXTBUTTON[FR]&#NEXTBUTTON

This solution assumes that ENDS1 is the question displayed at the end of Section 1. Any other condition could be used. Several conditions could be created as well as in this:

    # RECALL NEXTBUTTON =
       #> [QUESTION(ENDS1)][EN]Submit Section 1[FR]Envoyer la section 1
       #> [QUESTION(ENDS2)][EN]Submit Section 2[FR]Envoyer la section 2
       #> [ELSE][EN]Next page >>>[FR]Page suivante >>>
    # Forward text = [EN]&#NEXTBUTTON[FR]&#NEXTBUTTON

   

Adapting to mobile devices

Problem

You want to display questionnaires differently on (small) mobile devices.

Solution

Use conditionnally recalled values in # STYLESHEET and # TEMPLATE, and turn on # ACTIVATE THE MOBILE MODE.

Discussion

Mobile devices are smart phones and tablets which can display questionnaire pages but offer much less screen space than typical computer displays. Displaying a questionnaire in a readable form on these devices requires formatting for the screen size and, sometimes, modifying the flow of the questionnaire.

The key ingredients are:

  • the use of a conditional # STYLESHEET:
    this pound instruction specifies the stylesheet file that will be used to format the pages. A stylesheet can be devised with larger fonts (for example) for mobile devices. Then a conditional # STYLESHEET instruction can be built, for example:
    # STYLESHEET = &#GETSTYLESHEET
    # RECALL GETSTYLESHEET = [MOBILE().EQ.0]mainstyles.css[ELSE]stylesformobiles.css
  • the possible use of a conditional # TEMPLATE:
    more involved but with more control, the # TEMPLATE instruction defines the entire page format. Like the # STYLESHEET instruction, it can use a conditional recall to specify a different # TEMPLATE for mobile devices
  • the possible use of # ACTIVATE THE MOBILE MODE:
    # ACTIVATE THE MOBILE MODE = yes shows simplified pages to mobile devices: headers and footers are not displayed; # GROUP and # TABLE structures are also ignored such that every question is displayed on a separate page.

Adapting to mobile devices

Problem

You want to display questionnaires differently on (small) mobile devices.

Solution

Use conditionnally recalled values in # STYLESHEET and # TEMPLATE, and turn on # ACTIVATE THE MOBILE MODE.

Discussion

Mobile devices are smart phones and tablets which can display questionnaire pages but offer much less screen space than typical computer displays. Displaying a questionnaire in a readable form on these devices requires formatting for the screen size and, sometimes, modifying the flow of the questionnaire.

The key ingredients are:

  • the use of a conditional # STYLESHEET:
    this pound instruction specifies the stylesheet file that will be used to format the pages. A stylesheet can be devised with larger fonts (for example) for mobile devices. Then a conditional # STYLESHEET instruction can be built, for example:
    # STYLESHEET = &#GETSTYLESHEET
    # RECALL GETSTYLESHEET = [MOBILE().EQ.0]mainstyles.css[ELSE]stylesformobiles.css
  • the possible use of a conditional # TEMPLATE:
    more involved but with more control, the # TEMPLATE instruction defines the entire page format. Like the # STYLESHEET instruction, it can use a conditional recall to specify a different # TEMPLATE for mobile devices
  • the possible use of # ACTIVATE THE MOBILE MODE:
    # ACTIVATE THE MOBILE MODE = yes shows simplified pages to mobile devices: headers and footers are not displayed; # GROUP and # TABLE structures are also ignored such that every question is displayed on a separate page.

   

Displaying questions in random order

Problem

You want to display a set of questions in random order.

Solution

Use the PERMUTATION pound instruction.

Discussion

One page of the CallWeb technical documentation explains the details of the PERMUTATION pound instruction.

Displaying questions in random order

Problem

You want to display a set of questions in random order.

Solution

Use the PERMUTATION pound instruction.

Discussion

One page of the CallWeb technical documentation explains the details of the PERMUTATION pound instruction.

   

Ending a questionnaire without an exit URL

Problem

You want to end a questionnaire without re-direction to an exit URL.

Solution

  • create a "Thank you" question that has a minimum and a maximum of zero responses (no answer expected) as well as the following characteristics: BACKWALL (no Previous button) and CULDESAC (no Next button);
  • add a BLANK question (called QCOMPLETED hereafter) right before the Thank You question; it will act as a marker that the respondent has reached the end of the questionnaire;
  • add a # Complete Case instruction with "QCOMPLETED=1" as the condition; this will ease the determination of how many questionnaires were completed and supply this information in cwstats.cgi;
  • if you want to disallow returning into the questionnaire, add a # Noreturn Condition instruction with "QCOMPLETED.EQ.1" as the condition; this will ensure that anyone who has seen the Thank You page cannot go back into the questionnaire.

Discussion

The # URL instruction defines a URL where respondents are redirected once they leave the questionnaire (i.e., once they attempt to go past the last question). In standard circumstances where there is no need to redirect to another site or elsewhere on the current site (and where respondent's workstations are set up to disallow URL redirection), it may be preferable to NOT use # URL and to display the Thank You page in a standard question.

One advantage of this approach is that there is no need to create static pages for the Thank You page; another one is that you are certain that the Thank You page will have the same appearance as the rest of the questionnaire.

Ending a questionnaire without an exit URL

Problem

You want to end a questionnaire without re-direction to an exit URL.

Solution

  • create a "Thank you" question that has a minimum and a maximum of zero responses (no answer expected) as well as the following characteristics: BACKWALL (no Previous button) and CULDESAC (no Next button);
  • add a BLANK question (called QCOMPLETED hereafter) right before the Thank You question; it will act as a marker that the respondent has reached the end of the questionnaire;
  • add a # Complete Case instruction with "QCOMPLETED=1" as the condition; this will ease the determination of how many questionnaires were completed and supply this information in cwstats.cgi;
  • if you want to disallow returning into the questionnaire, add a # Noreturn Condition instruction with "QCOMPLETED.EQ.1" as the condition; this will ensure that anyone who has seen the Thank You page cannot go back into the questionnaire.

Discussion

The # URL instruction defines a URL where respondents are redirected once they leave the questionnaire (i.e., once they attempt to go past the last question). In standard circumstances where there is no need to redirect to another site or elsewhere on the current site (and where respondent's workstations are set up to disallow URL redirection), it may be preferable to NOT use # URL and to display the Thank You page in a standard question.

One advantage of this approach is that there is no need to create static pages for the Thank You page; another one is that you are certain that the Thank You page will have the same appearance as the rest of the questionnaire.

   

Exiting to a different URL depending upon circumstances

Problem

You want to redirect the respondent to a different URL depending upon circumstances.

Solution

  • calculate a URL based on existing data/responses;
  • recall the calculated URL in the URL pound instruction.

Discussion

If a respondent goes beyond the last question in the questionnaire, the URL pound instruction is used to redirect the browser session to another URL (e.g., a client Web site, a thank-you page, etc.). Only one URL is allowed for each language of the questionnaire.

What if the respondent must be redirected to a different URL based on responses offered in the questionnaire (or prepopulated data)? Calculate a URL into an open-end part and recall it into the URL pound instruction, as in:

    # Url = [EN]{$ACALCURL}[FR]{$ACALCURL}
    CALCURL CALCUL
    % question
       ACALCURL = ( $SOMEANSWER == 1 ) ? "http://somedomain.com/" : "http://someotherdomain.com/"
    % note
    % categories

    % skips
    % condition
    % open
       1 = C50 1 50
    !

Remember that recalls in URL pound instructions must use the Perl recall syntax.

As for the calculation expression above, it uses the Perl condition testing function with a logical expression within the parentheses followed by the result of a "true" condition, a colon and the result for a "false" condition.

Exiting to a different URL depending upon circumstances

Problem

You want to redirect the respondent to a different URL depending upon circumstances.

Solution

  • calculate a URL based on existing data/responses;
  • recall the calculated URL in the URL pound instruction.

Discussion

If a respondent goes beyond the last question in the questionnaire, the URL pound instruction is used to redirect the browser session to another URL (e.g., a client Web site, a thank-you page, etc.). Only one URL is allowed for each language of the questionnaire.

What if the respondent must be redirected to a different URL based on responses offered in the questionnaire (or prepopulated data)? Calculate a URL into an open-end part and recall it into the URL pound instruction, as in:

    # Url = [EN]{$ACALCURL}[FR]{$ACALCURL}
    CALCURL CALCUL
    % question
       ACALCURL = ( $SOMEANSWER == 1 ) ? "http://somedomain.com/" : "http://someotherdomain.com/"
    % note
    % categories

    % skips
    % condition
    % open
       1 = C50 1 50
    !

Remember that recalls in URL pound instructions must use the Perl recall syntax.

As for the calculation expression above, it uses the Perl condition testing function with a logical expression within the parentheses followed by the result of a "true" condition, a colon and the result for a "false" condition.

   

Suspending a questionnaire, then performing some task

Problem

You want the respondent to be able to suspend the questionnaire and you want the questionnaire to perform some action after that request.

Solution

The following code suspends the questionnaire and sends an e-mail to the respondent.

    # Bouton stop = oui,INTERRUPT
    TOTERMINE BLANK
    ## This question skips ahead to the end of the questionnaire to isolate the INTERRUPT series
    ## and avoid that one enters it at the end of the interview
    % Question
    % Note
    % Categories

    % Skips
       1 = ~TERMINE
    % Condition
    % Open part
    ! ####################################################################################################
    INTERRUPT NOSTOP
    ## This question is displayed only after clicking the Stop button
    ## The NOSTOP parameter is so that the Stop button is not displayed on that screen
    % Question
       Please enter an e-mail address where we will send you a link to come back to the questionnaire.
    % Note
    % Categories

    % Skips
    % Condition
    % Open part
       1 = E40 1 40
    ! ####################################################################################################
    INTERRUPT2 CALCUL
    ## This question e-mails the text of INTERRUPT3 to the respondent at the AINTERRUPT address
    % Question
       AINTERRUPT2 = email("info\@callweb.ca",$AINTERRUPT,"INTERRUPT3")
    % Note
    % Categories

    % Skips
    % Condition
    % Open part
       1 = C40 1 40
    ! ####################################################################################################
    INTERRUPT3 EMAIL
    ## Text of the e-mail sent out
    % Question
       Resuming your questionnaire
    % Note
       Please click the following link to resume your questionnaire:<P>
       http://{$contexte{host}}{$contexte{script}}?_proj={$_proj}&_lang={$_lang}&_telkey={$_telkey}
    % Categories
    % Skips
    % Condition
    % Open part
    ! ####################################################################################################
    INTERRUPT4 CALCUL
    ## Closes the browser window
    % Question
       dump = ferme_le_navigateur()
    % Note
    % Categories
    % Skips
    % Condition
    % Open part
    ! ####################################################################################################
    TERMINE BLANK
    ## This question ends the questionnaire.
    % Question
    % Note
    % Categories

    % Skips
    % Condition
    % Open part
    ! ####################################################################################################

Discussion

Sometimes, respondents want to stop filling out a questionnaire and resume later. This is easily achieved by re-using a link that was sent with an invitation to take part in the survey if access is password-protected (# Type enquete = 2). However, if access was open or if an access-code was provided upon entry, respondents are unlikely to have the information required to return.

In these circumstances, the Stop button can be displayed with a pound instruction like the following:

    # Bouton stop = oui,INTERRUPT

which activates the Stop button and instructs CallWeb to go directly to the INTERRUPT question once the button is clicked (clicking the Stop button also saves the data already provided on that page but does not validate them).

The interruption destination question can do anything. The following code shows how to request that an e-mail address be supplied and how to send a message containing a link back to the questionnaire — right where the interview left off.

Suspending a questionnaire, then performing some task

Problem

You want the respondent to be able to suspend the questionnaire and you want the questionnaire to perform some action after that request.

Solution

The following code suspends the questionnaire and sends an e-mail to the respondent.

    # Bouton stop = oui,INTERRUPT
    TOTERMINE BLANK
    ## This question skips ahead to the end of the questionnaire to isolate the INTERRUPT series
    ## and avoid that one enters it at the end of the interview
    % Question
    % Note
    % Categories

    % Skips
       1 = ~TERMINE
    % Condition
    % Open part
    ! ####################################################################################################
    INTERRUPT NOSTOP
    ## This question is displayed only after clicking the Stop button
    ## The NOSTOP parameter is so that the Stop button is not displayed on that screen
    % Question
       Please enter an e-mail address where we will send you a link to come back to the questionnaire.
    % Note
    % Categories

    % Skips
    % Condition
    % Open part
       1 = E40 1 40
    ! ####################################################################################################
    INTERRUPT2 CALCUL
    ## This question e-mails the text of INTERRUPT3 to the respondent at the AINTERRUPT address
    % Question
       AINTERRUPT2 = email("info\@callweb.ca",$AINTERRUPT,"INTERRUPT3")
    % Note
    % Categories

    % Skips
    % Condition
    % Open part
       1 = C40 1 40
    ! ####################################################################################################
    INTERRUPT3 EMAIL
    ## Text of the e-mail sent out
    % Question
       Resuming your questionnaire
    % Note
       Please click the following link to resume your questionnaire:<P>
       http://{$contexte{host}}{$contexte{script}}?_proj={$_proj}&_lang={$_lang}&_telkey={$_telkey}
    % Categories
    % Skips
    % Condition
    % Open part
    ! ####################################################################################################
    INTERRUPT4 CALCUL
    ## Closes the browser window
    % Question
       dump = ferme_le_navigateur()
    % Note
    % Categories
    % Skips
    % Condition
    % Open part
    ! ####################################################################################################
    TERMINE BLANK
    ## This question ends the questionnaire.
    % Question
    % Note
    % Categories

    % Skips
    % Condition
    % Open part
    ! ####################################################################################################

Discussion

Sometimes, respondents want to stop filling out a questionnaire and resume later. This is easily achieved by re-using a link that was sent with an invitation to take part in the survey if access is password-protected (# Type enquete = 2). However, if access was open or if an access-code was provided upon entry, respondents are unlikely to have the information required to return.

In these circumstances, the Stop button can be displayed with a pound instruction like the following:

    # Bouton stop = oui,INTERRUPT

which activates the Stop button and instructs CallWeb to go directly to the INTERRUPT question once the button is clicked (clicking the Stop button also saves the data already provided on that page but does not validate them).

The interruption destination question can do anything. The following code shows how to request that an e-mail address be supplied and how to send a message containing a link back to the questionnaire — right where the interview left off.

   

Displaying a question only at the call centre

Problem

You want to display a question only to interviewers at the call centre.

Solution

Insert a display condition using the browser's computer IP address as in:

    { $contexte{ip}=~/^192\.168\./ }

Discussion

The situation is this: you are running a dual mode survey (concurrently on the Web and as a telephone survey) and you want some questions to be reserved to interviewers in the call centre (i.e., on the internal network as opposed to on the open Internet).

To do so, implement a display condition which identifies internal network IP addresses, such as the one above. A question's display condition is a logical expression describing the circumstances when the question is displayed. Make sure to use the right network address segment as the internal network may use a different addressing range.

In the example above, the logical expression must use Perl syntax in order to make use of the $contexte data — hence the use of the braces around the condition. The Perl "=~" operator means "contains" while the circumflex accent at the beginning of the pattern anchors the comparison to the beginning of the IP address.

Note that the same logic can be used to calculate a value (with a CALCUL question) dependent upon the IP address of the respondent's computer.

Displaying a question only at the call centre

Problem

You want to display a question only to interviewers at the call centre.

Solution

Insert a display condition using the browser's computer IP address as in:

    { $contexte{ip}=~/^192\.168\./ }

Discussion

The situation is this: you are running a dual mode survey (concurrently on the Web and as a telephone survey) and you want some questions to be reserved to interviewers in the call centre (i.e., on the internal network as opposed to on the open Internet).

To do so, implement a display condition which identifies internal network IP addresses, such as the one above. A question's display condition is a logical expression describing the circumstances when the question is displayed. Make sure to use the right network address segment as the internal network may use a different addressing range.

In the example above, the logical expression must use Perl syntax in order to make use of the $contexte data — hence the use of the braces around the condition. The Perl "=~" operator means "contains" while the circumflex accent at the beginning of the pattern anchors the comparison to the beginning of the IP address.

Note that the same logic can be used to calculate a value (with a CALCUL question) dependent upon the IP address of the respondent's computer.

   

Displaying a question in only one language

Problem

You want to display a question only to respondents using a certain language.

Solution

Insert a display condition such as:

    LANGUAGE(FR)

Discussion

Say some text must be displayed only in French. Using the question's display condition (a logical expression describing the circumstances when the question is displayed) and the special operator LANGUAGE(), it is possible to zoom in on cases being filled out only in one language.

In a multi-language questionnaire, it would also be possible to identify more than one language using the following syntax:

    LANGUAGE(FR) .OR. LANGUAGE(EN)

The LANGUAGE() operator is synonym with LANGUE().

Displaying a question in only one language

Problem

You want to display a question only to respondents using a certain language.

Solution

Insert a display condition such as:

    LANGUAGE(FR)

Discussion

Say some text must be displayed only in French. Using the question's display condition (a logical expression describing the circumstances when the question is displayed) and the special operator LANGUAGE(), it is possible to zoom in on cases being filled out only in one language.

In a multi-language questionnaire, it would also be possible to identify more than one language using the following syntax:

    LANGUAGE(FR) .OR. LANGUAGE(EN)

The LANGUAGE() operator is synonym with LANGUE().

   

Testing that responses total 100%

Problem

You want to make sure that responses to a series of questions total 100% (or whatever other value).

Solution

Use a Test pound instruction as in:

    # Test WhateverName =
      #> [TRIGGER]Q1A
      #> [CONDITION](AQ1A+AQ1B+AQ1C+AQ1D).NE.100
      #> [MESSAGE]
      #> [EN]Please make sure the numbers total 100
      #> [FR]Veuillez vous assurer que le total est 100
      #> [TYPE]TABLE

Discussion

The Test pound instruction can be used to implement any specialised logical test. In the one above, the test is triggered by receiving an answer to question Q1A. The test is then implemented: the message is displayed if the sum of four open-end parts is not equal to 100. The "type" parameter specifies that the error message should be delivered in the context of questions presented in a table format (assuming that the four open-end questions were displayed in this manner).

The test can be more or less complex. It can also use other questions in the questionnaire. For example, the following condition verifies whether the sum of four answers exceeds the value of another answer:

    #> [CONDITION](AQ3A+AQ3B+AQ3C).GT.AQ2

Testing that responses total 100%

Problem

You want to make sure that responses to a series of questions total 100% (or whatever other value).

Solution

Use a Test pound instruction as in:

    # Test WhateverName =
      #> [TRIGGER]Q1A
      #> [CONDITION](AQ1A+AQ1B+AQ1C+AQ1D).NE.100
      #> [MESSAGE]
      #> [EN]Please make sure the numbers total 100
      #> [FR]Veuillez vous assurer que le total est 100
      #> [TYPE]TABLE

Discussion

The Test pound instruction can be used to implement any specialised logical test. In the one above, the test is triggered by receiving an answer to question Q1A. The test is then implemented: the message is displayed if the sum of four open-end parts is not equal to 100. The "type" parameter specifies that the error message should be delivered in the context of questions presented in a table format (assuming that the four open-end questions were displayed in this manner).

The test can be more or less complex. It can also use other questions in the questionnaire. For example, the following condition verifies whether the sum of four answers exceeds the value of another answer:

    #> [CONDITION](AQ3A+AQ3B+AQ3C).GT.AQ2

   

Changing to another language programmatically

Problem

In the course of a questionnaire, you want to change to another language based on an answer to a previous question.

Solution

Use a CALCUL question as in:

    Q1
    % question
      [EN]In which language do you want to fill out the questionnaire?
      [FR]En quelle langue voulez-vous compléter le questionnaire?
    % note
    % categories
      [EN]English[FR]Anglais
      [EN]French[FR]Français
    % skips
    % condition
    % open-end
    ! ==================================================
    SWITCH CALCUL
    % question
      SWITCH =
      {
      $formdata{_lang} = ( $QLANGUE == 1 ) ? "FR" : "EN";
      $_callweb = _insere_dans_chaine("_lang",$formdata{_lang},$_callweb);
      }
    % note
    % categories
    % skips
    % condition
    % open-end
    ! ==================================================

Discussion

The $_lang system variable controls the language in which the questionnaire is displayed. It contains a two-letter code from the ISO classification of languages.

The CALCUL question above attributes "EN" to $_lang if Q1 equals 1 and "FR" otherwise. This scheme can be extended to more languages or other sources of data such as prepopulated information: if you have imported information on the respondent's preferred language of communication, you can use it in a similar CALCUL to adjust the language of the questionnaire accordingly.

Changing to another language programmatically

Problem

In the course of a questionnaire, you want to change to another language based on an answer to a previous question.

Solution

Use a CALCUL question as in:

    Q1
    % question
      [EN]In which language do you want to fill out the questionnaire?
      [FR]En quelle langue voulez-vous compléter le questionnaire?
    % note
    % categories
      [EN]English[FR]Anglais
      [EN]French[FR]Français
    % skips
    % condition
    % open-end
    ! ==================================================
    SWITCH CALCUL
    % question
      SWITCH =
      {
      $formdata{_lang} = ( $QLANGUE == 1 ) ? "FR" : "EN";
      $_callweb = _insere_dans_chaine("_lang",$formdata{_lang},$_callweb);
      }
    % note
    % categories
    % skips
    % condition
    % open-end
    ! ==================================================

Discussion

The $_lang system variable controls the language in which the questionnaire is displayed. It contains a two-letter code from the ISO classification of languages.

The CALCUL question above attributes "EN" to $_lang if Q1 equals 1 and "FR" otherwise. This scheme can be extended to more languages or other sources of data such as prepopulated information: if you have imported information on the respondent's preferred language of communication, you can use it in a similar CALCUL to adjust the language of the questionnaire accordingly.

   

Branching out to any URL

Problem

In the course of a questionnaire, you branch to a URL of your choosing, inside CallWeb or outside.

Solution

Use a GOTOURL question as in:

    Q1 GOTOURL
    % question
      URL = http://callweb.ca,
      RETURN_TO = Q10
    % note
    % categories
     
    % skips
    % condition
    % open-end
    ! ==================================================

Discussion

A GOTOURL question is used to calculate a URL that the questionnaire will branch to right away. The relevant options are placed in the question text segment of the question definition. Options are separated by commas. Relevant options are as follows:

  • URL = one of the following
    • a specific URL like http://callweb.ca
    • URLs specific to languages like [EN]http://callweb.ca[FR]http://circum.com
    • URLs produced by a recall of some sort like # RECALL SOMEURL = [Q1.EQ.1]http://callweb.ca[ELSE]http://circum.com or any other valid recall syntax
  • RETURN_TO or RETOUR_A = one of the following:
    • the name of a variable to go to upon returning to this questionnaire at the GOTOURL question
    • a recall of some sort that will produce a valid question name
    • (In the absence of this RETURN_TO value, returning to a questionnaire that ended on the GOTOURL question will immediately flow out to the URL calculated by the GOTOURL question.)

The display condition is respected but simple skips are not since the "skip" will be to the calculated URL.

The value of the first response category is stored in the data base if the GOTOURL question was activated. Thus, at least one category is required.

Branching out to any URL

Problem

In the course of a questionnaire, you branch to a URL of your choosing, inside CallWeb or outside.

Solution

Use a GOTOURL question as in:

    Q1 GOTOURL
    % question
      URL = http://callweb.ca,
      RETURN_TO = Q10
    % note
    % categories
     
    % skips
    % condition
    % open-end
    ! ==================================================

Discussion

A GOTOURL question is used to calculate a URL that the questionnaire will branch to right away. The relevant options are placed in the question text segment of the question definition. Options are separated by commas. Relevant options are as follows:

  • URL = one of the following
    • a specific URL like http://callweb.ca
    • URLs specific to languages like [EN]http://callweb.ca[FR]http://circum.com
    • URLs produced by a recall of some sort like # RECALL SOMEURL = [Q1.EQ.1]http://callweb.ca[ELSE]http://circum.com or any other valid recall syntax
  • RETURN_TO or RETOUR_A = one of the following:
    • the name of a variable to go to upon returning to this questionnaire at the GOTOURL question
    • a recall of some sort that will produce a valid question name
    • (In the absence of this RETURN_TO value, returning to a questionnaire that ended on the GOTOURL question will immediately flow out to the URL calculated by the GOTOURL question.)

The display condition is respected but simple skips are not since the "skip" will be to the calculated URL.

The value of the first response category is stored in the data base if the GOTOURL question was activated. Thus, at least one category is required.

   

Determining how many responses were given to a question

Problem

You want to find out how many answers a respondent has given to a certain question.

Solution

Use a CALCUL question as in:

    number = n_selections($QUESTION,1,2,3,4,5)

Discussion

The n_selections function returns the number of selections a respondent has made in question QUESTION among the list of codes which follows the name of the question.

Determining how many responses were given to a question

Problem

You want to find out how many answers a respondent has given to a certain question.

Solution

Use a CALCUL question as in:

    number = n_selections($QUESTION,1,2,3,4,5)

Discussion

The n_selections function returns the number of selections a respondent has made in question QUESTION among the list of codes which follows the name of the question.

   

Determining how many responses were given to a series of questions

Problem

You want to find out how many answers a respondent has given to a series question.

Solution

Use a CALCUL question as in:

    number = n_such(1,4,9,"Q1-Q10")

Discussion

The n_such function returns the number of selections a respondent has made in a list of questions among the list of codes which precedes the name of the question. In the example above, only answers with values 1, 4 and 9 are counted and only answers provided to questions lying between Q1 and Q10 (including these boundaries) are included. The syntax of the list of questions allows for ranges (as above) as well as individual question names and concatenations using commas as in: "Q1-Q5, Q7, Q8-Q10".

Determining how many responses were given to a series of questions

Problem

You want to find out how many answers a respondent has given to a series question.

Solution

Use a CALCUL question as in:

    number = n_such(1,4,9,"Q1-Q10")

Discussion

The n_such function returns the number of selections a respondent has made in a list of questions among the list of codes which precedes the name of the question. In the example above, only answers with values 1, 4 and 9 are counted and only answers provided to questions lying between Q1 and Q10 (including these boundaries) are included. The syntax of the list of questions allows for ranges (as above) as well as individual question names and concatenations using commas as in: "Q1-Q5, Q7, Q8-Q10".

   

Adding a specialized function to CallWeb

Problem

You want to add a calculation capability that does not exist in CallWeb.

Solution

Create a Perl subroutine and put it in a file called cwplugins.pl alongside cwroutines.pl.

Discussion

While CallWeb offers a wealth of functions and allows for any Perl expression as part of CALCULations, it is possible that one particular treatment may not be readily available in the CallWeb toolbox. You may extend CallWeb possibilities by programming a Perl function to do what you want and adding it to a file called "cwplugins.pl" which must be located in the same directory as cwroutines.pl. Note that the cwplugins.pl file must end with a line containing "1;" (i.e., compiling this library will return a "true" value).

Values from the questionnaire data stream can be passed to these functions and results returned to CallWeb variables through CALCUL questions as in the following example: Q3 = somefunction($Q1,$Q2) which provides the function named "somefunction" with the current values of questions Q1 and Q2, and store a result in question Q3.

Adding a specialized function to CallWeb

Problem

You want to add a calculation capability that does not exist in CallWeb.

Solution

Create a Perl subroutine and put it in a file called cwplugins.pl alongside cwroutines.pl.

Discussion

While CallWeb offers a wealth of functions and allows for any Perl expression as part of CALCULations, it is possible that one particular treatment may not be readily available in the CallWeb toolbox. You may extend CallWeb possibilities by programming a Perl function to do what you want and adding it to a file called "cwplugins.pl" which must be located in the same directory as cwroutines.pl. Note that the cwplugins.pl file must end with a line containing "1;" (i.e., compiling this library will return a "true" value).

Values from the questionnaire data stream can be passed to these functions and results returned to CallWeb variables through CALCUL questions as in the following example: Q3 = somefunction($Q1,$Q2) which provides the function named "somefunction" with the current values of questions Q1 and Q2, and store a result in question Q3.

   

Bringing data to a common unit

Problem

You want to bring to a common unit of measurement numeric data which was collected on different units.

Solution

There are two possibilities:

  • if the unit multipliers are integers, assign the multiplier to the unit and perform a multiplication;
  • if the unit multipliers are not integers, user the ternary operator in a calculation.

Discussion

First, a note of clarification on the actual problem. This recipe discusses the situation where numeric data was collected (e.g., one's weight or one's salary) which requires a numeric value as well as a unit of measurement (e.g., inches vs. centimiters, dollars per hour, per week, per month), typically collected using a unit-type question. The questionnaire designer wants to calculate a unique value which will bring all responses to a common unit (e.g., centimiters or dollars per year).

If the unit multipliers are integers, it is possible to assign the multiplier as the category code for the unit and to simply multiply this code with the numeric value supplied. For example:

    SALARY
    % question
       What is your salary?
    % note
    % categories
       *998*N*amount
       *40*per hour
       *5*per day
       *1*per week
       *999*Don't know/no answer
    % skips
    % condition
    % open
       998 = N7.2 1 5000
    ! ==============================================================
    PERWEEK CALCUL
    % question
       APERWEEK = $SALARY * $ASALARY
    % note
    % categories
     
    % skips
    % condition
       SALARY.EQ.1,5,40
    % open
       1 = N7.2 1 5000 FORMAT=DOLLAR2
    ! ==============================================================

If the unit multipliers are not integers (a limitation of answer category codes), use a calculation and the ternary operator (the "implicit if" operator). For example:

    HEIGHT
    % question
       How tall are you?
    % note
    % categories
       *998*N*amount
       *1*centimeters
       *2*inches
       *999*Don't know/no answer
    % skips
    % condition
    % open
       998 = N7.2 48 200
    ! ==============================================================
    INCENTIMETERS CALCUL
    % question
       AINCENTIMETERS = $AHEIGHT * ( ( $HEIGHT == 1 ) ? 1 : 2.54 )
    % note
    % categories
     
    % skips
    % condition
       HEIGHT.EQ.1,2
    % open
       1 = N7.2 48 200 FORMAT=NUM0
    ! ==============================================================

Bringing data to a common unit

Problem

You want to bring to a common unit of measurement numeric data which was collected on different units.

Solution

There are two possibilities:

  • if the unit multipliers are integers, assign the multiplier to the unit and perform a multiplication;
  • if the unit multipliers are not integers, user the ternary operator in a calculation.

Discussion

First, a note of clarification on the actual problem. This recipe discusses the situation where numeric data was collected (e.g., one's weight or one's salary) which requires a numeric value as well as a unit of measurement (e.g., inches vs. centimiters, dollars per hour, per week, per month), typically collected using a unit-type question. The questionnaire designer wants to calculate a unique value which will bring all responses to a common unit (e.g., centimiters or dollars per year).

If the unit multipliers are integers, it is possible to assign the multiplier as the category code for the unit and to simply multiply this code with the numeric value supplied. For example:

    SALARY
    % question
       What is your salary?
    % note
    % categories
       *998*N*amount
       *40*per hour
       *5*per day
       *1*per week
       *999*Don't know/no answer
    % skips
    % condition
    % open
       998 = N7.2 1 5000
    ! ==============================================================
    PERWEEK CALCUL
    % question
       APERWEEK = $SALARY * $ASALARY
    % note
    % categories
     
    % skips
    % condition
       SALARY.EQ.1,5,40
    % open
       1 = N7.2 1 5000 FORMAT=DOLLAR2
    ! ==============================================================

If the unit multipliers are not integers (a limitation of answer category codes), use a calculation and the ternary operator (the "implicit if" operator). For example:

    HEIGHT
    % question
       How tall are you?
    % note
    % categories
       *998*N*amount
       *1*centimeters
       *2*inches
       *999*Don't know/no answer
    % skips
    % condition
    % open
       998 = N7.2 48 200
    ! ==============================================================
    INCENTIMETERS CALCUL
    % question
       AINCENTIMETERS = $AHEIGHT * ( ( $HEIGHT == 1 ) ? 1 : 2.54 )
    % note
    % categories
     
    % skips
    % condition
       HEIGHT.EQ.1,2
    % open
       1 = N7.2 48 200 FORMAT=NUM0
    ! ==============================================================

   

Calculating time spent on a page

Problem

You want to calculate how long someone has spent on a certain page.

Solution

There are two possibilities:

  • calculate the current time before the page and again after and store the difference;
  • activate BASEclicks.

Discussion

The first approach works well if you need only a few time counters or if you need to make reference to the time value within the questionnaire (e.g., to remind the respondent to take their time if they appear to be speeding through the questionnaire). It consists of adding two computed questions:

  • one before the page to be timed stores the current time in seconds; it may look like this:

    T1 CALCUL
    % question
       AT1 = time()
    % note
    % categories
     
    % skips
    % condition
    % open part
       1 = N10.0
    ! ==============================================================

  • a second computed question is placed after the page to be timed; it calculates and stores the difference in seconds between the current time and the time in AT1; it may look like this:

    T2 CALCUL
    % question
       AT2 = time() - $AT1
    % note
    % categories
     
    % skips
    % condition
    % open part
       1 = N10.0
    ! ==============================================================

The second approach uses BASEclicks. Once the BASEclick project is compiled and available, CallWeb systematically stores the time when each page is shown in each project which include the "# BASEclicks = yes" instruction; CallWeb also stores the time lapsed since the previous entry for a given IP address in a given project. The data in the BASEclicks project can be analyzed using any and all of the existing CallWeb utility programs. Beware of the fact that this project grows quite rapidly to many thousands of records on a busy server.

Calculating time spent on a page

Problem

You want to calculate how long someone has spent on a certain page.

Solution

There are two possibilities:

  • calculate the current time before the page and again after and store the difference;
  • activate BASEclicks.

Discussion

The first approach works well if you need only a few time counters or if you need to make reference to the time value within the questionnaire (e.g., to remind the respondent to take their time if they appear to be speeding through the questionnaire). It consists of adding two computed questions:

  • one before the page to be timed stores the current time in seconds; it may look like this:

    T1 CALCUL
    % question
       AT1 = time()
    % note
    % categories
     
    % skips
    % condition
    % open part
       1 = N10.0
    ! ==============================================================

  • a second computed question is placed after the page to be timed; it calculates and stores the difference in seconds between the current time and the time in AT1; it may look like this:

    T2 CALCUL
    % question
       AT2 = time() - $AT1
    % note
    % categories
     
    % skips
    % condition
    % open part
       1 = N10.0
    ! ==============================================================

The second approach uses BASEclicks. Once the BASEclick project is compiled and available, CallWeb systematically stores the time when each page is shown in each project which include the "# BASEclicks = yes" instruction; CallWeb also stores the time lapsed since the previous entry for a given IP address in a given project. The data in the BASEclicks project can be analyzed using any and all of the existing CallWeb utility programs. Beware of the fact that this project grows quite rapidly to many thousands of records on a busy server.

   

Debugging a complex calculation

Problem

You need to determine why a calculation does not work.

Solution

Use "print" commands in an anonymous subroutine structure.

Discussion

We realize this discussion is technical; so are complex calculations.

Most of the problems with complex calculations have to do with the absence of the dollar sign as prefix to variable names (required in the Perl context), with variables not being properly initialized and with improper mathematical syntax. Therefore, use the following debugging guidelines.

  • make sure that each variable (or question) name is prefixed with a dollar sign. The following DOES NOT WORK: Q2 = Q1+2 but this does work: Q2 = $Q1+2
  • "print" the value of key variables used using the following syntax (put the entire calculation within braces and end each statement with a semi-colon):
      Q2 =
      {
      print "\$Q1 = $Q1";
      return $Q1+2
      }
  • in the case of a complex expression, break it up and print intermediate results, such as:
      Q5 =
      {
      print "\$Q1 = $Q1<BR>\$Q2 = $Q2<BR>\$Q3 = $Q3<BR>";
      print "Q1+Q2 = ".($Q1 + $Q2)."<BR>";
      return $Q1+$Q2+$Q3
      }

Debugging a complex calculation

Problem

You need to determine why a calculation does not work.

Solution

Use "print" commands in an anonymous subroutine structure.

Discussion

We realize this discussion is technical; so are complex calculations.

Most of the problems with complex calculations have to do with the absence of the dollar sign as prefix to variable names (required in the Perl context), with variables not being properly initialized and with improper mathematical syntax. Therefore, use the following debugging guidelines.

  • make sure that each variable (or question) name is prefixed with a dollar sign. The following DOES NOT WORK: Q2 = Q1+2 but this does work: Q2 = $Q1+2
  • "print" the value of key variables used using the following syntax (put the entire calculation within braces and end each statement with a semi-colon):
      Q2 =
      {
      print "\$Q1 = $Q1";
      return $Q1+2
      }
  • in the case of a complex expression, break it up and print intermediate results, such as:
      Q5 =
      {
      print "\$Q1 = $Q1<BR>\$Q2 = $Q2<BR>\$Q3 = $Q3<BR>";
      print "Q1+Q2 = ".($Q1 + $Q2)."<BR>";
      return $Q1+$Q2+$Q3
      }

   

Recalculating a CALCUL question en masse

Problem

You need to recalculate a CALCUL question for several data records.

Solution

Use cwnav.cgi.

Discussion

One of the features of cwnav.cgi is labelled "Update using a CALCUL question"; it is displayed only if the questionnaire includes CALCUL questions. Using the drop-down list, select which CALCUL questions to process and click the "Action!" button. On the follow-up page, insert the "UPDATE" (all caps) keyword to confirm the recalculation request. Only records which fit the case selection criteria at the top of the cwnav.cgi page are accessed. Only records which suit the CALCUL question display condition are affected; skips within the questionnaire are not taken into account. System variables (starting with an underscore) cannot be modified by this procedure.

Examples of use of this procedure include:

  • a CALCUL field was added part-way through the data collection process;
  • a calculation needs to be performed only after all data have been collected;
  • a CALCUL field was found to be erroneous and needs to be recomputed.

Recalculating a CALCUL question en masse

Problem

You need to recalculate a CALCUL question for several data records.

Solution

Use cwnav.cgi.

Discussion

One of the features of cwnav.cgi is labelled "Update using a CALCUL question"; it is displayed only if the questionnaire includes CALCUL questions. Using the drop-down list, select which CALCUL questions to process and click the "Action!" button. On the follow-up page, insert the "UPDATE" (all caps) keyword to confirm the recalculation request. Only records which fit the case selection criteria at the top of the cwnav.cgi page are accessed. Only records which suit the CALCUL question display condition are affected; skips within the questionnaire are not taken into account. System variables (starting with an underscore) cannot be modified by this procedure.

Examples of use of this procedure include:

  • a CALCUL field was added part-way through the data collection process;
  • a calculation needs to be performed only after all data have been collected;
  • a CALCUL field was found to be erroneous and needs to be recomputed.

   

Verifying an e-mail address

Problem

You need to make sure that an e-mail address obtained in a questionnaire is valid.

Solution

Use the test_email_address function in a CALCUL question.

Discussion

Getting e-mail addresses as part of Web data collection or telephone surveys is notoriously error-prone. The test_email_address function verifies whether a given e-mail address is (probably) deliverable. This function requires that the Net::Telnet Perl module be installed on the server. Calling this function is as simple as:

    ATEST = test_email_address($AEMAILADDRESS)

where ATEST is the open-end part that contains the result of the test and AEMAILADDRESS is the name of the open-end part containing the e-mail address.

The function returns one of the following codes. The CallWeb script can then branch to the appropriate next place in the questionnaire using habitual flow control tools like branching and display conditions.

test_email_address return codes
CodeMeaning
-2There is no mail (MX) server for the domain name of the e-mail address.
-1Net:Telnet is not installed on the server.
0The e-mail address is invalid.
1The e-mail address appears valid.
2The e-mail address appears valid but it is on a Microsoft Exchange server and, therefore, the result may be questionnable.

 

Verifying an e-mail address

Problem

You need to make sure that an e-mail address obtained in a questionnaire is valid.

Solution

Use the test_email_address function in a CALCUL question.

Discussion

Getting e-mail addresses as part of Web data collection or telephone surveys is notoriously error-prone. The test_email_address function verifies whether a given e-mail address is (probably) deliverable. This function requires that the Net::Telnet Perl module be installed on the server. Calling this function is as simple as:

    ATEST = test_email_address($AEMAILADDRESS)

where ATEST is the open-end part that contains the result of the test and AEMAILADDRESS is the name of the open-end part containing the e-mail address.

The function returns one of the following codes. The CallWeb script can then branch to the appropriate next place in the questionnaire using habitual flow control tools like branching and display conditions.

test_email_address return codes
CodeMeaning
-2There is no mail (MX) server for the domain name of the e-mail address.
-1Net:Telnet is not installed on the server.
0The e-mail address is invalid.
1The e-mail address appears valid.
2The e-mail address appears valid but it is on a Microsoft Exchange server and, therefore, the result may be questionnable.

 

   

Adding or subtracting time from a date-time value

Problem

You need to add or subtract time (seconds, minutes, hours, days) from a date-time value expressed as YYYYMMDD or YYYYMMDDHHMMSS.

Solution

Use the add_to_date function in a CALCUL question.

Discussion

CallWeb stores dates as YYYYMMDD values and date-time as YYYYMMDDHHMMSS values. For example, $contexte{date} contains the current date formatted as YYYYMMDD (currently 20240418) and $contexte{dateheure} contains the current date and time formatted as YYYYMMDDHHMMSS (currently 20240418145548). While comparisons are easy, other manipulations of such values are not. Adding one day to 20091231 returns 20100101, for example. The add_to_date function helps with date and date-time calculations.

Here is the general syntax of the add_to_date function:

ADATE2 = add_to_date($ADATE1,value,type_of_offset)

where

  • ADATE2 is the output variable of the CALCUL question
  • $ADATE1 is the initial date or date-time value; a date comprises 8 characters (YYYYMMDD) while a date-time includes 14 characters (YYYYMMDDHHMMSS)
  • value is the amount that needs to be added or subtracted; to add one day, value would be 1; this value can be a constant (like "1") or a variable (like "$Q1");
  • type_of_offset expresses the type of amount to add or subtract; it is a keyword among the following: DAY, DAYS, JOUR, JOURS, HOUR, HOURS, HEURE, HEURES, MINUTE, MINUTES, SECOND, SECONDS, SECONDE, SECONDES

So, add_to_date(20091231,1,"DAY") adds one day to New Year's Eve 2009 while add_to_date(20091231235959,1,"SECOND") moves from 2009 to 2010. Nevative values subtract from the stated date.

The result returned by add_to_date has the same length as the input value. Thus, adding less than one day to a YYYYMMDD value will return the same date value.

Adding or subtracting time from a date-time value

Problem

You need to add or subtract time (seconds, minutes, hours, days) from a date-time value expressed as YYYYMMDD or YYYYMMDDHHMMSS.

Solution

Use the add_to_date function in a CALCUL question.

Discussion

CallWeb stores dates as YYYYMMDD values and date-time as YYYYMMDDHHMMSS values. For example, $contexte{date} contains the current date formatted as YYYYMMDD (currently 20240418) and $contexte{dateheure} contains the current date and time formatted as YYYYMMDDHHMMSS (currently 20240418145548). While comparisons are easy, other manipulations of such values are not. Adding one day to 20091231 returns 20100101, for example. The add_to_date function helps with date and date-time calculations.

Here is the general syntax of the add_to_date function:

ADATE2 = add_to_date($ADATE1,value,type_of_offset)

where

  • ADATE2 is the output variable of the CALCUL question
  • $ADATE1 is the initial date or date-time value; a date comprises 8 characters (YYYYMMDD) while a date-time includes 14 characters (YYYYMMDDHHMMSS)
  • value is the amount that needs to be added or subtracted; to add one day, value would be 1; this value can be a constant (like "1") or a variable (like "$Q1");
  • type_of_offset expresses the type of amount to add or subtract; it is a keyword among the following: DAY, DAYS, JOUR, JOURS, HOUR, HOURS, HEURE, HEURES, MINUTE, MINUTES, SECOND, SECONDS, SECONDE, SECONDES

So, add_to_date(20091231,1,"DAY") adds one day to New Year's Eve 2009 while add_to_date(20091231235959,1,"SECOND") moves from 2009 to 2010. Nevative values subtract from the stated date.

The result returned by add_to_date has the same length as the input value. Thus, adding less than one day to a YYYYMMDD value will return the same date value.

   

Drawing a data chart

Problem

You want to draw a chart from CallWeb data and insert it in a questionnaire.

Solution

Use the calc_graph function in a CALCUL question.

Discussion

CallWeb uses the Highcharts JavaScript system to produce data charts. The calc_graph function provides an easy way to build these charts.

calc_graph can be callwed in a CALCUL question or in an <execute></execute> substitution. The call works this way:

    calc_graph ( option => value, option => value ); [note the use of the "=>" operator to separate option names from their values, and of commas to separate the option-value pairs.]

Here is the list of available options; only one is mandatory.

StatusOption name ValueDefault value
Mandatoryfield=>name of the question to chartNone
Optionaltype=>type of chart"bar" (no other type available at this time)
Optionalbreakdown=>name of the question acting as data breakdownNone
Optionalsubset_in_sql=>MySQL-syntax case selection expressionNone
Optional_proj=>CallWeb project name from which the data are drawnCurrent project
Optionalprint=>0 | no | non | 1 | oui | yesYes. The JavaScript code for displaying the chart can be saved in a variable: $mychart = calc_graph ( print => no, option => value, option => value );
Optionalmaxscale=>maximum numeric value depicted on the y-axisDetermined by Highcharts
Optionalmaxtitlelength=>maximum number of characters in the title256
Optionalmaxsubtitlelength=>maximum number of characters in the subtitle256
Optionalminwidth=>minimum width of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts
Optionalmaxwidth=>maximum width of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts
Optionalminheight=>minimum height of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts
Optionalmaxheight=>maximum height of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts

Drawing a data chart

Problem

You want to draw a chart from CallWeb data and insert it in a questionnaire.

Solution

Use the calc_graph function in a CALCUL question.

Discussion

CallWeb uses the Highcharts JavaScript system to produce data charts. The calc_graph function provides an easy way to build these charts.

calc_graph can be callwed in a CALCUL question or in an <execute></execute> substitution. The call works this way:

    calc_graph ( option => value, option => value ); [note the use of the "=>" operator to separate option names from their values, and of commas to separate the option-value pairs.]

Here is the list of available options; only one is mandatory.

StatusOption name ValueDefault value
Mandatoryfield=>name of the question to chartNone
Optionaltype=>type of chart"bar" (no other type available at this time)
Optionalbreakdown=>name of the question acting as data breakdownNone
Optionalsubset_in_sql=>MySQL-syntax case selection expressionNone
Optional_proj=>CallWeb project name from which the data are drawnCurrent project
Optionalprint=>0 | no | non | 1 | oui | yesYes. The JavaScript code for displaying the chart can be saved in a variable: $mychart = calc_graph ( print => no, option => value, option => value );
Optionalmaxscale=>maximum numeric value depicted on the y-axisDetermined by Highcharts
Optionalmaxtitlelength=>maximum number of characters in the title256
Optionalmaxsubtitlelength=>maximum number of characters in the subtitle256
Optionalminwidth=>minimum width of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts
Optionalmaxwidth=>maximum width of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts
Optionalminheight=>minimum height of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts
Optionalmaxheight=>maximum height of the chart in pixels (px) or percentage of the page (%)Determined by Highcharts

   

Closing data collection at a particular date or time

Problem

You want to forbid access to a questionnaire past a certain date or time.

Solution

Use the "Deny access if" pound instruction as in:

    # Deny access if = {$contexte{date}>=20060612}

Discussion

The "Deny access if" pound instruction contains a logical condition which denies access to the questionnaire if it is true. This condition is tested every time callweb.cgi receives a request. In the example above, access is denied if the current date is June 12, 2006 or later. Other $contexte information can also be used to control access.

In the example above, the logical expression must use Perl syntax in order to make use of the $contexte data — hence the use of the braces around the condition.

The "Deny access if" pound instruction is the strongest barrier to entry since it is tested with every call to callweb.cgi. Alternative methods (using questionnaire logic) can only be implemented at specific places in the script.

This instruction displays system message 28 which is fully customizable, like any other system message.

Closing data collection at a particular date or time

Problem

You want to forbid access to a questionnaire past a certain date or time.

Solution

Use the "Deny access if" pound instruction as in:

    # Deny access if = {$contexte{date}>=20060612}

Discussion

The "Deny access if" pound instruction contains a logical condition which denies access to the questionnaire if it is true. This condition is tested every time callweb.cgi receives a request. In the example above, access is denied if the current date is June 12, 2006 or later. Other $contexte information can also be used to control access.

In the example above, the logical expression must use Perl syntax in order to make use of the $contexte data — hence the use of the braces around the condition.

The "Deny access if" pound instruction is the strongest barrier to entry since it is tested with every call to callweb.cgi. Alternative methods (using questionnaire logic) can only be implemented at specific places in the script.

This instruction displays system message 28 which is fully customizable, like any other system message.

   

Controlling access to a questionnaire by IP address

Problem

You want to allow access to a questionnaire only to workstations at a certain IP address.

Solution

Use the "Deny access if" pound instruction as in:

    # Deny access if = {$contexte{ip}!~/^55\.54\.53/}

Discussion

The "Deny access if" pound instruction contains a logical condition which denies access to the questionnaire if it is true. This condition is tested every time callweb.cgi receives a request. In the example above, access is denied if the IP address of the computer accessing the questionnaire does not begin with 55.54.53. Other $contexte information can also be used to control access.

In the example above, the logical expression must use Perl syntax in order to make use of the $contexte data — hence the use of the braces around the condition. The Perl "!~" operator means "does not contain" while the circumflex accent at the beginning of the pattern anchors the comparison to the beginning of the IP address.

The "Deny access if" pound instruction is the strongest barrier to entry since it is tested with every call to callweb.cgi. Alternative methods (using questionnaire logic) can only be implemented at specific places in the script.

This instruction displays system message 28 which is fully customizable, like any other system message.

Controlling access to a questionnaire by IP address

Problem

You want to allow access to a questionnaire only to workstations at a certain IP address.

Solution

Use the "Deny access if" pound instruction as in:

    # Deny access if = {$contexte{ip}!~/^55\.54\.53/}

Discussion

The "Deny access if" pound instruction contains a logical condition which denies access to the questionnaire if it is true. This condition is tested every time callweb.cgi receives a request. In the example above, access is denied if the IP address of the computer accessing the questionnaire does not begin with 55.54.53. Other $contexte information can also be used to control access.

In the example above, the logical expression must use Perl syntax in order to make use of the $contexte data — hence the use of the braces around the condition. The Perl "!~" operator means "does not contain" while the circumflex accent at the beginning of the pattern anchors the comparison to the beginning of the IP address.

The "Deny access if" pound instruction is the strongest barrier to entry since it is tested with every call to callweb.cgi. Alternative methods (using questionnaire logic) can only be implemented at specific places in the script.

This instruction displays system message 28 which is fully customizable, like any other system message.

   

Closing access once a certain number of completed questionnaires is reached

Problem

You want to disallow access to a questionnaire once a certain number of questionnaires have been completed.

Solution

There are two solutions to this problem:

  • use a QUOTA question;
  • use a "Deny access if" pound instruction.

Discussion

The purpose of a QUOTA question is to flow questionnaires to an alternate route (typically to a "Thanks anyway" page) once a certain number of questionnaires have been completed in a certain group (or in the entire field work). The syntax of QUOTA questions is fully explained in the CallWeb documentation. The key advantage of QUOTA questions compared to the second solution proposed below is that they are easily sensitive to complex situations and they are resource-efficient in that they are only tested when they are reached.

The second solution is to add a "Deny access if" pound instruction which returns "true" once a certain number of completed questionnaires are found in the data base. The syntax of this instruction could be as follows:

    # Deny access if = {_nrecords_mysql("project_name","WHERE QEND=1")>=300}

In this example, access to the questionnaire is denied when 300 cases have "QEND=1" in the data base of project "project_name". Of course, the WHERE clause could contain any legitimate MySQL expression based on the questions in the questionnaire. Note also that the expression is within braces since it is evaluated as a Perl expression. The main advantage of this solution is that it can block the project at any time while filling out the questionnaire since "Deny access if" is tested upon each call to callweb.cgi (hence the threshhold stated will not be exceeded); this is also the key weakness of this solution: the test (and the call to MySQL to count completed cases) is implemented often and could become a burden on the system if numbers grow very high. This instruction displays system message 28 which is fully customizable, like any other system message.

Closing access once a certain number of completed questionnaires is reached

Problem

You want to disallow access to a questionnaire once a certain number of questionnaires have been completed.

Solution

There are two solutions to this problem:

  • use a QUOTA question;
  • use a "Deny access if" pound instruction.

Discussion

The purpose of a QUOTA question is to flow questionnaires to an alternate route (typically to a "Thanks anyway" page) once a certain number of questionnaires have been completed in a certain group (or in the entire field work). The syntax of QUOTA questions is fully explained in the CallWeb documentation. The key advantage of QUOTA questions compared to the second solution proposed below is that they are easily sensitive to complex situations and they are resource-efficient in that they are only tested when they are reached.

The second solution is to add a "Deny access if" pound instruction which returns "true" once a certain number of completed questionnaires are found in the data base. The syntax of this instruction could be as follows:

    # Deny access if = {_nrecords_mysql("project_name","WHERE QEND=1")>=300}

In this example, access to the questionnaire is denied when 300 cases have "QEND=1" in the data base of project "project_name". Of course, the WHERE clause could contain any legitimate MySQL expression based on the questions in the questionnaire. Note also that the expression is within braces since it is evaluated as a Perl expression. The main advantage of this solution is that it can block the project at any time while filling out the questionnaire since "Deny access if" is tested upon each call to callweb.cgi (hence the threshhold stated will not be exceeded); this is also the key weakness of this solution: the test (and the call to MySQL to count completed cases) is implemented often and could become a burden on the system if numbers grow very high. This instruction displays system message 28 which is fully customizable, like any other system message.

   

Giving restricted access to some individuals

Problem

You want to give restricted access to some projects and/or to some modules to some individuals.

Solution

  • create a new directory at the same level as the main utility directory;
  • place all required utilities in that directory;
  • make all of these utilities executable;
  • add a # Visible depuis = utility_directory_name pound instruction in each project script that must be accessible from that utility directory;
  • recompile the projects with this new pound instruction.

Discussion

Only the main utilities directory (defined in the CallWeb configuration file) has access to all projects. Even in the case of that directory, only modules that are available in it can be used (obviously).

Creating a secondary utilities directory offers the possibility of putting only the required modules in it, thereby restricting which actions users of that directory can take.

Also, only projects which explicitly list the secondary utilities directory (via the "Visible depuis" pound instruction) can be accessed from that directory.

Giving restricted access to some individuals

Problem

You want to give restricted access to some projects and/or to some modules to some individuals.

Solution

  • create a new directory at the same level as the main utility directory;
  • place all required utilities in that directory;
  • make all of these utilities executable;
  • add a # Visible depuis = utility_directory_name pound instruction in each project script that must be accessible from that utility directory;
  • recompile the projects with this new pound instruction.

Discussion

Only the main utilities directory (defined in the CallWeb configuration file) has access to all projects. Even in the case of that directory, only modules that are available in it can be used (obviously).

Creating a secondary utilities directory offers the possibility of putting only the required modules in it, thereby restricting which actions users of that directory can take.

Also, only projects which explicitly list the secondary utilities directory (via the "Visible depuis" pound instruction) can be accessed from that directory.

   

Password-protecting a directory

Problem

You want to request a password to access a certain directory via a Web-browser.

Solution

Use the Apache .htaccess feature.

Discussion

.htaccess files provide a way to make configuration changes on a per-directory basis. A file, containing one or more configuration directives, is placed in a particular document directory, and the directives apply to that directory, and all subdirectories thereof. Password protection is one of the features offered by .htaccess files.

Password-protecting a directory

Problem

You want to request a password to access a certain directory via a Web-browser.

Solution

Use the Apache .htaccess feature.

Discussion

.htaccess files provide a way to make configuration changes on a per-directory basis. A file, containing one or more configuration directives, is placed in a particular document directory, and the directives apply to that directory, and all subdirectories thereof. Password protection is one of the features offered by .htaccess files.

   

Providing short links to questionnaires

Problem

You want to set up very short links to questionnaires.

Solution

Use the Apache RedirectMatch feature.

Discussion

CallWeb's short URLs allow for compact URLs. The syntax of short URLs is as follows

    http://domain.ext/callweb.cgi?language:project:telkey:first_question
    http://domain.ext/callweb.cgi?en:survey:123456:Q1

While the first two pieces of information are mandatory (the language and the project), the telkey and the first question values are not; they should be supplied if the context requires it.

Once could dispense with the reference to callweb.cgi in the URL by including callweb.cgi in the list of allowed starting pages in the Apache configuration. This is done by adding a DirectoryIndex instruction to the Apache configuration file:

    DirectoryIndex index.html callweb.cgi

The short URL above would then become http://domain.ext?en:survey:123456:Q1.

Of course, one would be well advised to use a short domain name rather than a long one. For example, http://97.ca translates into necessarily shorter and easier to communicate URLs than http://surveycenter.yourpreferredsupplier.qc.ca.

Another strategy is to use Apache's RedirectMatch instruction; it is extremely powerful. It can rewrite a URL according to components of the address supplied. For example, the following accepts the URL http://callweb.ca/survey and redirects the user to the following address http://callweb.ca/productionsite/callweb.cgi?_lang=EN&_proj=thissurvey:

    RedirectMatch ^/survey$ http://callweb.ca/productionsite/callweb.cgi?_lang=EN&_proj=thissurvey

This instruction takes the URL http://callweb.ca/survey/en/abcd and redirects to http://callweb.ca/productionsite/callweb.cgi?_lang=en&_proj=thissurvey&_telkey=abcd:

    RedirectMatch ^/survey/(..)/(.*)$ http://callweb.ca/productionsite/callweb.cgi?_lang=$1&_proj=thissurvey&_telkey=$2

Also, if no language parameter is passed along, CallWeb defaults to the browser preferred language (determined by the respondent), assuming it exists in the questionnaire. So, the RedirectMatch instruction above can be rewritten to avoid the language parameter altogether and to let CallWeb serve the respondent's preferred language:

    RedirectMatch ^/survey/(.*)$ http://callweb.ca/productionsite/callweb.cgi?_proj=thissurvey&_telkey=$1

This way http://callweb.ca/survey/abcd becomes http://callweb.ca/productionsite/callweb.cgi?_proj=thissurvey&_telkey=abcd and the respondent gets his/her preferred language if it exists (and the questionnaire default language if it does not).

Providing short links to questionnaires

Problem

You want to set up very short links to questionnaires.

Solution

Use the Apache RedirectMatch feature.

Discussion

CallWeb's short URLs allow for compact URLs. The syntax of short URLs is as follows

    http://domain.ext/callweb.cgi?language:project:telkey:first_question
    http://domain.ext/callweb.cgi?en:survey:123456:Q1

While the first two pieces of information are mandatory (the language and the project), the telkey and the first question values are not; they should be supplied if the context requires it.

Once could dispense with the reference to callweb.cgi in the URL by including callweb.cgi in the list of allowed starting pages in the Apache configuration. This is done by adding a DirectoryIndex instruction to the Apache configuration file:

    DirectoryIndex index.html callweb.cgi

The short URL above would then become http://domain.ext?en:survey:123456:Q1.

Of course, one would be well advised to use a short domain name rather than a long one. For example, http://97.ca translates into necessarily shorter and easier to communicate URLs than http://surveycenter.yourpreferredsupplier.qc.ca.

Another strategy is to use Apache's RedirectMatch instruction; it is extremely powerful. It can rewrite a URL according to components of the address supplied. For example, the following accepts the URL http://callweb.ca/survey and redirects the user to the following address http://callweb.ca/productionsite/callweb.cgi?_lang=EN&_proj=thissurvey:

    RedirectMatch ^/survey$ http://callweb.ca/productionsite/callweb.cgi?_lang=EN&_proj=thissurvey

This instruction takes the URL http://callweb.ca/survey/en/abcd and redirects to http://callweb.ca/productionsite/callweb.cgi?_lang=en&_proj=thissurvey&_telkey=abcd:

    RedirectMatch ^/survey/(..)/(.*)$ http://callweb.ca/productionsite/callweb.cgi?_lang=$1&_proj=thissurvey&_telkey=$2

Also, if no language parameter is passed along, CallWeb defaults to the browser preferred language (determined by the respondent), assuming it exists in the questionnaire. So, the RedirectMatch instruction above can be rewritten to avoid the language parameter altogether and to let CallWeb serve the respondent's preferred language:

    RedirectMatch ^/survey/(.*)$ http://callweb.ca/productionsite/callweb.cgi?_proj=thissurvey&_telkey=$1

This way http://callweb.ca/survey/abcd becomes http://callweb.ca/productionsite/callweb.cgi?_proj=thissurvey&_telkey=abcd and the respondent gets his/her preferred language if it exists (and the questionnaire default language if it does not).

   

Offering an "Unsubscribe" mechanism

Problem

You want to offer a method for respondents to unsubscribe from future invitation e-mails.

Solution

Add an "unsubscribe" calculation to the questionnaire and a link to it in invitation e-mail messages.

Discussion

It might be counter-intuitive, but offering a way for potential respondents to exclude themselves from invitation e-mail messages is actually a good thing, for a number of reasons. Offering a way to unsubscribe:

  • can increase the sense of trust between the participant and the survey manager;
  • can reduce the risk that a participant flags an invitation message as spam, thereby improving the survey manager reputation with key Internet service providers;
  • ensures that the information needed to unsubscribe is available (i.e., the _telkey and the project name);
  • leaves a trace of who has opted out;
  • reduces the resources devoted to unsubscribing since respondents do it themselves;
  • is simply polite.

Offering an unsubscribe mechanism is a two-step process. First, create a calculation and an information screen for this purpose in the questionnaire. Make sure that this section of the questionnaire is isolated from the rest of the questionnaire so that it is not reached in the normal course of filling out the questionnaire. For example:

    ISOLATE0 BLANK
    ## If ever reached, this question simply skips over the Unsubscribe block
    % question
    % note
    % categories

    % skips
       1 = ISOLATE1
    % condition
    % open-end
    ! ==================================================
    UNSUB BACKWALL
    % question
       [EN]Are you sure that you want to unsubscribe?
       [FR]Êtes-vous certain(e) de vouloir retirer votre nom de la liste?
    % note
    % categories
       [EN]Yes[FR]Oui
       [EN]No[FR]Non
    % skips
    % condition
    % open-end
    ! ==================================================
    UNSUB0 CALCUL
    ## Closes the browser if the respondent declined unsubscribing
    % question
       UNSUB0 = ferme_le_navigateur()
    % note
    % categories
    % skips
    % condition
       UNSUB.EQ.2
    % open-end
    ! ==================================================
    UNSUB1 CALCUL
    ## Adds "_unsub" to the e-mail address, thereby making it non-deliverable
    % question
       AEMAIL = $AEMAIL."_UNSUB"
    % note
    % categories
    % skips
    % condition
    % open-end
    ! ==================================================
    UNSUB2 MIN=0 MAX=0 BACKWALL CULDESAC
    % question
       [EN]You have been unsubscribed. Thank you.
       [FR]Votre nom a été retiré de la liste. Merci.
    % note
    % categories
    % skips
    % condition
    % open-end
    ! ==================================================
    ISOLATE1 BLANK
    ## This is the end of the Unsubscribe block
    % question
    % note
    % categories

    % skips
    % condition
    % open-end
    ! ==================================================

The calculation can be anything that suits your needs. The solution above adds "_UNSUB" to the e-mail address so that the address is no longer deliverable. This has the advantage of keeping the address information for future reference (i.e., "email@domain.com" becomes "email@domain.com_UNSUB" but the original e-mail address is still accessible in this field) and of requiring no other intervention to avoid further mailings to this respondent.

Second, insert a link in the invitation messages to trigger that portion of the questionnaire. In the example above, the link could look like this:

    <a href="http://domain.com/cwx.cgi?en:PROJECT:{$_telkey}:UNSUB">Unsubscribe</a>

This link opens the case referenced by the current _telkey, in project PROJECT, at question UNSUB, and displays the unsubscribe confirmation message.

A URL to the unsubscribe feature in the questionnaire can be supplied in the "list unsubscribe" option of cwemail.cgi and in the Autoemail pound instruction. Some Web mail system recognize this instruction and display a special button using the URL. A default value for "list unsubscribe" can be defined in the "list unsubscribe" pound instruction.

Offering an "Unsubscribe" mechanism

Problem

You want to offer a method for respondents to unsubscribe from future invitation e-mails.

Solution

Add an "unsubscribe" calculation to the questionnaire and a link to it in invitation e-mail messages.

Discussion

It might be counter-intuitive, but offering a way for potential respondents to exclude themselves from invitation e-mail messages is actually a good thing, for a number of reasons. Offering a way to unsubscribe:

  • can increase the sense of trust between the participant and the survey manager;
  • can reduce the risk that a participant flags an invitation message as spam, thereby improving the survey manager reputation with key Internet service providers;
  • ensures that the information needed to unsubscribe is available (i.e., the _telkey and the project name);
  • leaves a trace of who has opted out;
  • reduces the resources devoted to unsubscribing since respondents do it themselves;
  • is simply polite.

Offering an unsubscribe mechanism is a two-step process. First, create a calculation and an information screen for this purpose in the questionnaire. Make sure that this section of the questionnaire is isolated from the rest of the questionnaire so that it is not reached in the normal course of filling out the questionnaire. For example:

    ISOLATE0 BLANK
    ## If ever reached, this question simply skips over the Unsubscribe block
    % question
    % note
    % categories

    % skips
       1 = ISOLATE1
    % condition
    % open-end
    ! ==================================================
    UNSUB BACKWALL
    % question
       [EN]Are you sure that you want to unsubscribe?
       [FR]Êtes-vous certain(e) de vouloir retirer votre nom de la liste?
    % note
    % categories
       [EN]Yes[FR]Oui
       [EN]No[FR]Non
    % skips
    % condition
    % open-end
    ! ==================================================
    UNSUB0 CALCUL
    ## Closes the browser if the respondent declined unsubscribing
    % question
       UNSUB0 = ferme_le_navigateur()
    % note
    % categories
    % skips
    % condition
       UNSUB.EQ.2
    % open-end
    ! ==================================================
    UNSUB1 CALCUL
    ## Adds "_unsub" to the e-mail address, thereby making it non-deliverable
    % question
       AEMAIL = $AEMAIL."_UNSUB"
    % note
    % categories
    % skips
    % condition
    % open-end
    ! ==================================================
    UNSUB2 MIN=0 MAX=0 BACKWALL CULDESAC
    % question
       [EN]You have been unsubscribed. Thank you.
       [FR]Votre nom a été retiré de la liste. Merci.
    % note
    % categories
    % skips
    % condition
    % open-end
    ! ==================================================
    ISOLATE1 BLANK
    ## This is the end of the Unsubscribe block
    % question
    % note
    % categories

    % skips
    % condition
    % open-end
    ! ==================================================

The calculation can be anything that suits your needs. The solution above adds "_UNSUB" to the e-mail address so that the address is no longer deliverable. This has the advantage of keeping the address information for future reference (i.e., "email@domain.com" becomes "email@domain.com_UNSUB" but the original e-mail address is still accessible in this field) and of requiring no other intervention to avoid further mailings to this respondent.

Second, insert a link in the invitation messages to trigger that portion of the questionnaire. In the example above, the link could look like this:

    <a href="http://domain.com/cwx.cgi?en:PROJECT:{$_telkey}:UNSUB">Unsubscribe</a>

This link opens the case referenced by the current _telkey, in project PROJECT, at question UNSUB, and displays the unsubscribe confirmation message.

A URL to the unsubscribe feature in the questionnaire can be supplied in the "list unsubscribe" option of cwemail.cgi and in the Autoemail pound instruction. Some Web mail system recognize this instruction and display a special button using the URL. A default value for "list unsubscribe" can be defined in the "list unsubscribe" pound instruction.

   

Setting up access with a modifiable password

Problem

You want questionnaire respondents to have the ability to change their login password.

Solution

Use the "Password" type of "# Survey Type".

Discussion

The "Password" "# Survey Type" is the only one that allows CallWeb to distinguish between a user name and a password, and to extend respondents the ability to change their password at will. Several other instructions affect how this survey type works. All of these considerations are listed below.

  • The password is stored in the _password field in the CallWeb data base [1]. It is encrypted in such a way that CallWeb has no way to reverse-engineer the name of the password. Therefore, if a user looses their password, that field must be blanked out by the CallWeb administrator and the user must choose a password upon re-entry.
  • By default, respondents are offered the possibility to change their password. This can be denied by adding the # Password change in type 7 = no instruction.
  • Several pound instructions determine the text shown on the login screen:
    • # Access request password in type 7 contains the text displayed to request a password. The default is "Your password, please."
    • # Access request new password in type 7 contains the text displayed offer the entry of a new password. The default is "If you want to change your password, enter the existing password above, then type a new password below twice."
    • # Access bad telkey pattern in type 7 contains the text displayed upon finding a password which does not conform to the pattern defined in # Access telkey pattern (see below). The default is "The password does not follow the expected pattern."
  • Some system messages also affect text on screen:
    • Message 39: "You must supply the same new password twice." is displayed if the respondent did not type the same new password in the two boxes.
    • Message 40: "You must supply a new password twice in the boxes below." is displayed when the _password field of the data base is empty (i.e., when a case password is not defined).
  • Upon prepopulation, the _password field of the CallWeb data base is prepopulated with the encoded value of _password field in the prepopulation data file. If a _password value is empty or if there is no _password field in the prepopulation file, the _password value is left empty in the data base and the next rule kicks in.
  • When the respondent accesses their case using their _telkey, they must supply their password. If the _password field is empty for that case in the data base, the respondent must immediately supply (in duplicate) a password to replace the empty value.
  • If you want to set passwords upon prepopulation, it is best to prepopulate the password twice (in _password and in a regular field) to be able to quote the password in an invitation e-mail message. The regular field could be blanked out using a CALCUL question upon initial entry in the questionnaire.
  • The questionnaire designer can define rules for the new passwords entered by respondents (these rules are not enforced by cwprepop.cgi when prepopulating _password) and place them in the # Access telkey pattern pound instruction. The rules take the form of regular expressions that must match then entire password [2]. If there are several rules (hence, several regular expressions), they must be separated by three consecutive dashes. For example, the following instruction contains three rules which state that there must be at least 6 characters in the password, that the password must contain at least one special character, and at least one number:
    # Access telkey pattern = ......+ --- .*[+-*&?%$@!].* --- .*[0-9].*
    The default pattern is .+ which means that there must be at least one character.
  • The values of _password are extracted by cwextr.cgi only in the .tcw file (that can be used to recreate a project integrally) since they are not meaningful in the other contexts.

[1] The _password field is available only after a new compilation. Existing projects compiled before the availability of this field can be converted simply by accessing each project using any of the administrative modules.

[2] I.e., when used, these regular expressions are surrounded by the characters "^" and "$". References to regular expressions: 1, 2

Setting up access with a modifiable password

Problem

You want questionnaire respondents to have the ability to change their login password.

Solution

Use the "Password" type of "# Survey Type".

Discussion

The "Password" "# Survey Type" is the only one that allows CallWeb to distinguish between a user name and a password, and to extend respondents the ability to change their password at will. Several other instructions affect how this survey type works. All of these considerations are listed below.

  • The password is stored in the _password field in the CallWeb data base [1]. It is encrypted in such a way that CallWeb has no way to reverse-engineer the name of the password. Therefore, if a user looses their password, that field must be blanked out by the CallWeb administrator and the user must choose a password upon re-entry.
  • By default, respondents are offered the possibility to change their password. This can be denied by adding the # Password change in type 7 = no instruction.
  • Several pound instructions determine the text shown on the login screen:
    • # Access request password in type 7 contains the text displayed to request a password. The default is "Your password, please."
    • # Access request new password in type 7 contains the text displayed offer the entry of a new password. The default is "If you want to change your password, enter the existing password above, then type a new password below twice."
    • # Access bad telkey pattern in type 7 contains the text displayed upon finding a password which does not conform to the pattern defined in # Access telkey pattern (see below). The default is "The password does not follow the expected pattern."
  • Some system messages also affect text on screen:
    • Message 39: "You must supply the same new password twice." is displayed if the respondent did not type the same new password in the two boxes.
    • Message 40: "You must supply a new password twice in the boxes below." is displayed when the _password field of the data base is empty (i.e., when a case password is not defined).
  • Upon prepopulation, the _password field of the CallWeb data base is prepopulated with the encoded value of _password field in the prepopulation data file. If a _password value is empty or if there is no _password field in the prepopulation file, the _password value is left empty in the data base and the next rule kicks in.
  • When the respondent accesses their case using their _telkey, they must supply their password. If the _password field is empty for that case in the data base, the respondent must immediately supply (in duplicate) a password to replace the empty value.
  • If you want to set passwords upon prepopulation, it is best to prepopulate the password twice (in _password and in a regular field) to be able to quote the password in an invitation e-mail message. The regular field could be blanked out using a CALCUL question upon initial entry in the questionnaire.
  • The questionnaire designer can define rules for the new passwords entered by respondents (these rules are not enforced by cwprepop.cgi when prepopulating _password) and place them in the # Access telkey pattern pound instruction. The rules take the form of regular expressions that must match then entire password [2]. If there are several rules (hence, several regular expressions), they must be separated by three consecutive dashes. For example, the following instruction contains three rules which state that there must be at least 6 characters in the password, that the password must contain at least one special character, and at least one number:
    # Access telkey pattern = ......+ --- .*[+-*&?%$@!].* --- .*[0-9].*
    The default pattern is .+ which means that there must be at least one character.
  • The values of _password are extracted by cwextr.cgi only in the .tcw file (that can be used to recreate a project integrally) since they are not meaningful in the other contexts.

[1] The _password field is available only after a new compilation. Existing projects compiled before the availability of this field can be converted simply by accessing each project using any of the administrative modules.

[2] I.e., when used, these regular expressions are surrounded by the characters "^" and "$". References to regular expressions: 1, 2

   

Two-factor authentication and more access control

Problem

You want to confirm that the respondent is the intended target for the questionnaire.

Solution

Use the "2FA" pound instruction.

Discussion

When the 2FA pound instruction exists and has been validated by compilation, CallWeb allows response into the questionnaire only once a two-factor authentication cookie has been set. This is done by entering a randomly generated code in the CallWeb script; the code is generated by CallWeb and sent to the respondent's email address. Let's see the details starting with the syntax of # 2FA.

    # 2FA =
      #> [EMAILQUESTION]name of an EMAIL question
      #> [EMAILADDRESS]name of the open-end part of the question containing the email address of the respondent
      #> [CODEPATTERN]code pattern
      #> [STORECODE]name of an open-ended STOCK question to store the random code
      #> [COLLECTCODE]name of the question used to collect the random code from the respondent
      #> [FROM]email address used to send the 2FA message
  • EMAILQUESTION: this is the name of an EMAIL question that will be used to transmit the random code to the respondent. It is EMAIL2FA in the example below.
  • EMAILADDRESS: this is the name of an open-ended part containing the email address of the respondent. This is the address where the message containing the random code will be sent.
  • CODEPATTERN: the random code used to authenticate the respondent can follow any pattern as defined by the syntax used by # Telkey pattern: sequence of letters corresponding to the number and type of characters used: [C]onsonant, [V]owel, [L]etter, [N]umeric, [S]pecial and [?] for any of these. It is common to use six-digit numbers for 2FA purposes; that would be a pattern of NNNNNN.
  • STORECODE: this is the name of the question that will store the random code in its open-ended part.
  • COLLECTCODE: this is the name of the question that will be used to ask the respondent to enter the random code.
  • FROM: this is the email address used to send the 2FA message. This is not a question; it is an actual address or a substitution

Here is a working example.

    # 2FA =
      #> [emailquestion]EMAIL2FA
      #> [emailaddress]EMAIL
      #> [codepattern]NNNNNN
      #> [storecode]STORE2FA
      #> [collectcode]COLLECT2FA
      #> [from]2fa@callweb.ca
    EMAIL2FA EMAIL
    % texte de la question
      Your confirmation code for {$contexte{projet}} / Votre code de confirmation pour {$contexte{projet}}
    % texte de la note
      <p>(Le français suit l'anglais.)</p>
      <p>------------------------------------------------------------</p>
      <p>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</p>
      <p>Please enter this six-digit code in the web form to confirm that you control this email address.</p>
      <p>&&ASTORE2FA</p>
      <p>------------------------------------------------------------</p>
      <p>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}} ».</p>
      <p>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</p>
      <p>&&ASTORE2FA</p>
       
        <html>

        <head></head>

        <body style='margin: 0px 0px 0px 0px; font-family: Arial, Helvetica, sans-serif; font-size: 18px; color: #000000;'>

        <table border="0" cellpadding="5" cellspacing="5" width="100%">

        <tr>
        <td width="48%" height="0"></td>
        <td width="4%" height="0"></td>
        <td width="48%" height="0"></td>
        </tr>

        <tr valign="top">
        <td>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</td>
        <td> </td>
        <td>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}}nbsp;».</td>
        </tr>

        <tr valign="top">
        <td>Please enter this six-digit code in the web form to confirm that you control this email address.</td>
        <td> </td>
        <td>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</td>
        </tr>

        <tr>
        <td colspan="3" align="center"><div style="font-size: 24px; background-color: red ;color: white;">&&ASTORE2FA</div></td>
        </tr>

        </table>

        </body>

        </html>
    % catégories de réponses
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    EMAIL STOCK
    % texte de la question
    % texte de la note
    % catégories de réponses
     
    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = E50 1 50
    ! ==================================================
    STORE2FA STOCK
    % texte de la question
    % texte de la note
    % catégories de réponses
     
    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = C10 1 10
    ! ==================================================
    COLLECT2FA
    % texte de la question
      [EN]A six-digit code has been emailed to &&AEMAIL to confirm that you are the owner of this address. Please enter this six-digit code below.
      [FR]Un code à six chiffres a été envoyé par courriel à l'adresse &&AEMAIL pour confirmer que vous êtes bien propriétaire de cette adresse. Veuillez saisir ce code à six chiffres ci-bas.
    % texte de la note
    % catégories de réponses
     
    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = C10 1 10
    ! ==================================================

Two-factor authentication and more access control

Problem

You want to confirm that the respondent is the intended target for the questionnaire.

Solution

Use the "2FA" pound instruction.

Discussion

When the 2FA pound instruction exists and has been validated by compilation, CallWeb allows response into the questionnaire only once a two-factor authentication cookie has been set. This is done by entering a randomly generated code in the CallWeb script; the code is generated by CallWeb and sent to the respondent's email address. Let's see the details starting with the syntax of # 2FA.

    # 2FA =
      #> [EMAILQUESTION]name of an EMAIL question
      #> [EMAILADDRESS]name of the open-end part of the question containing the email address of the respondent
      #> [CODEPATTERN]code pattern
      #> [STORECODE]name of an open-ended STOCK question to store the random code
      #> [COLLECTCODE]name of the question used to collect the random code from the respondent
      #> [FROM]email address used to send the 2FA message
  • EMAILQUESTION: this is the name of an EMAIL question that will be used to transmit the random code to the respondent. It is EMAIL2FA in the example below.
  • EMAILADDRESS: this is the name of an open-ended part containing the email address of the respondent. This is the address where the message containing the random code will be sent.
  • CODEPATTERN: the random code used to authenticate the respondent can follow any pattern as defined by the syntax used by # Telkey pattern: sequence of letters corresponding to the number and type of characters used: [C]onsonant, [V]owel, [L]etter, [N]umeric, [S]pecial and [?] for any of these. It is common to use six-digit numbers for 2FA purposes; that would be a pattern of NNNNNN.
  • STORECODE: this is the name of the question that will store the random code in its open-ended part.
  • COLLECTCODE: this is the name of the question that will be used to ask the respondent to enter the random code.
  • FROM: this is the email address used to send the 2FA message. This is not a question; it is an actual address or a substitution

Here is a working example.

    # 2FA =
      #> [emailquestion]EMAIL2FA
      #> [emailaddress]EMAIL
      #> [codepattern]NNNNNN
      #> [storecode]STORE2FA
      #> [collectcode]COLLECT2FA
      #> [from]2fa@callweb.ca
    EMAIL2FA EMAIL
    % texte de la question
      Your confirmation code for {$contexte{projet}} / Votre code de confirmation pour {$contexte{projet}}
    % texte de la note
      <p>(Le français suit l'anglais.)</p>
      <p>------------------------------------------------------------</p>
      <p>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</p>
      <p>Please enter this six-digit code in the web form to confirm that you control this email address.</p>
      <p>&&ASTORE2FA</p>
      <p>------------------------------------------------------------</p>
      <p>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}} ».</p>
      <p>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</p>
      <p>&&ASTORE2FA</p>
       
        <html>

        <head></head>

        <body style='margin: 0px 0px 0px 0px; font-family: Arial, Helvetica, sans-serif; font-size: 18px; color: #000000;'>

        <table border="0" cellpadding="5" cellspacing="5" width="100%">

        <tr>
        <td width="48%" height="0"></td>
        <td width="4%" height="0"></td>
        <td width="48%" height="0"></td>
        </tr>

        <tr valign="top">
        <td>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</td>
        <td> </td>
        <td>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}}nbsp;».</td>
        </tr>

        <tr valign="top">
        <td>Please enter this six-digit code in the web form to confirm that you control this email address.</td>
        <td> </td>
        <td>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</td>
        </tr>

        <tr>
        <td colspan="3" align="center"><div style="font-size: 24px; background-color: red ;color: white;">&&ASTORE2FA</div></td>
        </tr>

        </table>

        </body>

        </html>
    % catégories de réponses
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    EMAIL STOCK
    % texte de la question
    % texte de la note
    % catégories de réponses
     
    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = E50 1 50
    ! ==================================================
    STORE2FA STOCK
    % texte de la question
    % texte de la note
    % catégories de réponses
     
    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = C10 1 10
    ! ==================================================
    COLLECT2FA
    % texte de la question
      [EN]A six-digit code has been emailed to &&AEMAIL to confirm that you are the owner of this address. Please enter this six-digit code below.
      [FR]Un code à six chiffres a été envoyé par courriel à l'adresse &&AEMAIL pour confirmer que vous êtes bien propriétaire de cette adresse. Veuillez saisir ce code à six chiffres ci-bas.
    % texte de la note
    % catégories de réponses
     
    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = C10 1 10
    ! ==================================================

   

Controlled access on an open project

Problem

You want to constrain respondents to a single questionnaire but also want to allow unknown respondents in.

Solution

You could use # Control by cookie but another, maybe more robust, solution is to add a font-end project to control access.

Discussion

The # Control by cookie activates a cookie-based control system which prevents the creation of a second questionnaire by a given computer. However, it is limited to fully open projects, works in callweb.cgi only, not in cwx.cgi, and is not immuned to users deleting the cookie or using a second browser to access more questionnaires.

The general solution proposed here is to create a small project (hereby called the gatekeeper) that is used to control access to the second, real questionnaire. The gatekeeper project requests the respondent's email address and emails a one-time use code to authenticate the user at that address. Once the code is correctly entered, the gatekeeper project looks up the _telkey corresponding to the email address in the second project or creates a record in the second project, and flows the respondent to that record in the main project. This can work even if the second project is closed. It can also allow or disallow access by email addresses that are not already prepopped into the second project.

Gatekeeper project

Here are example code snipplets.

    EMAIL
    % Question
      [EN]Please enter your professional email address. <span class="NOBOLD">Please note that we are asking your email address only to assign you your own unique survey link while ensuring that you are the owner of that address. This will allow you to leave the survey and return to the last question you were on in case you cannot complete the survey in one sitting. Your email address will not be associated with your responses in any of the analysis and reporting.</span>
      [FR]Veuillez fournir votre adresse de courriel professionnel. <span class="NOBOLD">Veuillez noter que nous recueillons votre adresse de courriel uniquement pour vous attribuer un lien d'enquête propre à vous tout en nous assurant que vous êtes le propriétaire de cette adresse. Cela vous permettra de quitter le sondage et de revenir à la dernière question à laquelle vous répondiez au cas où vous ne pourriez pas répondre au sondage d'une traite. Votre adresse de courriel ne sera associée à vos réponses dans aucune des analyses et rapports.</span>
    % Note
    % Categories
      *1*[EN]Email[FR]Courriel
      *2*[EN]I prefer not to answer[FR]Je préfère ne pas répondre
    % Skips
      2 = COMPLETE
    % Condition
    % Open
      1 = E100 1 40
    ! ==============================================================
    TELKEY CALCUL
    % Question
      ATELKEY =
        {
        if ( n_dossiers("AEMAIL=\"$AEMAIL\"","MAINproject") == 0 )
          { put_values_in_case(_proj => "MAINproject", AEMAIL => $AEMAIL, ORIGINAL_SAMPLE => 2) }
        return pull_value("UNIQUE","_telkey","MAINproject","AEMAIL=\"$AEMAIL\"");
        }
    % Note
    % Categories

    % Skips
    % Condition
    % Open
    1 = C10 1 10
    ! ==============================================================
    STORE2FA CALCUL
    % texte de la question
      DUMP =
        {
        $STORE2FA = calcule_une_combinaison("NNNNNN");
        email("from\@example.com",$AEMAIL,"EMAIL2FA");
        }
    % texte de la note
    % catégories de réponses
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    EMAIL2FA EMAIL
    % texte de la question
      Your confirmation code for {$contexte{projet}} / Votre code de confirmation pour {$contexte{projet}}
    % texte de la note
      <p>(Le français suit l'anglais.)</p>
      <p>------------------------------------------------------------</p>
      <p>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</p>
      <p>Please enter this six-digit code in the web form to confirm that you control this email address.</p>
      <p>&@STORE2FA</p>
      <p>------------------------------------------------------------</p>
      <p>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}} ».</p>
      <p>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</p>
      <p>&@STORE2FA</p>

        <html>

        <head></head>

        <body style='margin: 0px 0px 0px 0px; font-family: Arial, Helvetica, sans-serif; font-size: 18px; color: #000000;'>

        <table border="0" cellpadding="5" cellspacing="5" width="100%">

        <tr>
        <td width="48%" height="0"></td>
        <td width="4%" height="0"></td>
        <td width="48%" height="0"></td>
        </tr>

        <tr valign="top">
        <td>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</td>
        <td> </td>
        <td>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}} ».</td>
        </tr>

        <tr valign="top">
        <td>Please enter this six-digit code in the web form to confirm that you control this email address.</td>
        <td> </td>
        <td>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</td>
        </tr>

        <tr>
        <td colspan="3" align="center"><div style="font-size: 24px; background-color: red ;color: white;">&@STORE2FA</div></td>
        </tr>

        </table>

        </body>

        </html>
    % catégories de réponses
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    COLLECT2FA
    % texte de la question
      [EN]A six-digit code has been emailed to &&AEMAIL to confirm that you are the owner of this address. Please enter this six-digit code below.
      [FR]Un code à six chiffres a été envoyé par courriel à l'adresse &&AEMAIL pour confirmer que vous êtes bien propriétaire de cette adresse. Veuillez saisir ce code à six chiffres ci-bas.
    % texte de la note
    % catégories de réponses

    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = C10 1 10
    ! ==================================================
    CODEOK CALCUL
    % texte de la question
      $CODEOK =
        {
        my $res = ( $ACOLLECT2FA eq $STORE2FA ) ? 1 : 0;
        $ACOLLECT2FA = "";
        $STORE2FA = "";
        return $res;
        }
    % texte de la note
    % catégories de réponses
      *0*[EN]Bad[FR]Mauvais
      *1*[EN]Good[FR]Bon
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    WRONGCODE BACKWALL
    % texte de la question
      [EN]The code you entered does not match the one emailed to &&AEMAIL. Do you want to get a new code?
      [FR]Le code que vous avez saisi ne correspond pas au code envoyé à &&AEMAIL. Voulez-vous recevoir un autre code?
    % texte de la note
    % catégories de réponses
      [EN]Yes[FR]Oui
      [EN]No[FR]Non
    % sauts inconditionnels
      1 = STORE2FA
    % condition
      CODEOK.EQ.0
    % partie ouverte
    ! ==================================================
    # URL = [EN]https://example.com/en/MAINproject/&&ATELKEY[FR]https://example.com/fr/MAINproject/&&ATELKEY
    QEND MIN=0 MAX=0 CULDESAC BACKWALL
    % texte de la question
      [EN]<p align="center">Thank you for your collaboration.<br />You may now close this window.</p>
      [FR]<p align="center">Merci pour votre collaboration.<br />Vous pouvez maintenant fermer cette fenêtre.</p>
    % texte de la note
    % catégories de réponses
    % sauts inconditionnels
    % condition
      EMAIL.EQ.2 .OR. WRONGCODE.EQ.2
    % partie ouverte
    ! ==================================================

Main project

The requirements of the main project are minimal.

  • it can be open if the designer wants to maintain the ability to access it directly; this kind of defeats the purpose though, so it is likely closed.
  • it contains a STOCK field to store the email address; this field is prepopulated with email addresses expected to fill out the questionnaire and, if so desired, it can accept new addresses supplied by the gatekeeper project.

Controlled access on an open project

Problem

You want to constrain respondents to a single questionnaire but also want to allow unknown respondents in.

Solution

You could use # Control by cookie but another, maybe more robust, solution is to add a font-end project to control access.

Discussion

The # Control by cookie activates a cookie-based control system which prevents the creation of a second questionnaire by a given computer. However, it is limited to fully open projects, works in callweb.cgi only, not in cwx.cgi, and is not immuned to users deleting the cookie or using a second browser to access more questionnaires.

The general solution proposed here is to create a small project (hereby called the gatekeeper) that is used to control access to the second, real questionnaire. The gatekeeper project requests the respondent's email address and emails a one-time use code to authenticate the user at that address. Once the code is correctly entered, the gatekeeper project looks up the _telkey corresponding to the email address in the second project or creates a record in the second project, and flows the respondent to that record in the main project. This can work even if the second project is closed. It can also allow or disallow access by email addresses that are not already prepopped into the second project.

Gatekeeper project

Here are example code snipplets.

    EMAIL
    % Question
      [EN]Please enter your professional email address. <span class="NOBOLD">Please note that we are asking your email address only to assign you your own unique survey link while ensuring that you are the owner of that address. This will allow you to leave the survey and return to the last question you were on in case you cannot complete the survey in one sitting. Your email address will not be associated with your responses in any of the analysis and reporting.</span>
      [FR]Veuillez fournir votre adresse de courriel professionnel. <span class="NOBOLD">Veuillez noter que nous recueillons votre adresse de courriel uniquement pour vous attribuer un lien d'enquête propre à vous tout en nous assurant que vous êtes le propriétaire de cette adresse. Cela vous permettra de quitter le sondage et de revenir à la dernière question à laquelle vous répondiez au cas où vous ne pourriez pas répondre au sondage d'une traite. Votre adresse de courriel ne sera associée à vos réponses dans aucune des analyses et rapports.</span>
    % Note
    % Categories
      *1*[EN]Email[FR]Courriel
      *2*[EN]I prefer not to answer[FR]Je préfère ne pas répondre
    % Skips
      2 = COMPLETE
    % Condition
    % Open
      1 = E100 1 40
    ! ==============================================================
    TELKEY CALCUL
    % Question
      ATELKEY =
        {
        if ( n_dossiers("AEMAIL=\"$AEMAIL\"","MAINproject") == 0 )
          { put_values_in_case(_proj => "MAINproject", AEMAIL => $AEMAIL, ORIGINAL_SAMPLE => 2) }
        return pull_value("UNIQUE","_telkey","MAINproject","AEMAIL=\"$AEMAIL\"");
        }
    % Note
    % Categories

    % Skips
    % Condition
    % Open
    1 = C10 1 10
    ! ==============================================================
    STORE2FA CALCUL
    % texte de la question
      DUMP =
        {
        $STORE2FA = calcule_une_combinaison("NNNNNN");
        email("from\@example.com",$AEMAIL,"EMAIL2FA");
        }
    % texte de la note
    % catégories de réponses
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    EMAIL2FA EMAIL
    % texte de la question
      Your confirmation code for {$contexte{projet}} / Votre code de confirmation pour {$contexte{projet}}
    % texte de la note
      <p>(Le français suit l'anglais.)</p>
      <p>------------------------------------------------------------</p>
      <p>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</p>
      <p>Please enter this six-digit code in the web form to confirm that you control this email address.</p>
      <p>&@STORE2FA</p>
      <p>------------------------------------------------------------</p>
      <p>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}} ».</p>
      <p>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</p>
      <p>&@STORE2FA</p>

        <html>

        <head></head>

        <body style='margin: 0px 0px 0px 0px; font-family: Arial, Helvetica, sans-serif; font-size: 18px; color: #000000;'>

        <table border="0" cellpadding="5" cellspacing="5" width="100%">

        <tr>
        <td width="48%" height="0"></td>
        <td width="4%" height="0"></td>
        <td width="48%" height="0"></td>
        </tr>

        <tr valign="top">
        <td>This message was sent to the email address found in your questionnaire for project "{$contexte{projet}}".</td>
        <td> </td>
        <td>Ce message a été envoyé à l'adresse de courriel identifiée dans votre questionnaire pour le projet « {$contexte{projet}} ».</td>
        </tr>

        <tr valign="top">
        <td>Please enter this six-digit code in the web form to confirm that you control this email address.</td>
        <td> </td>
        <td>Veuillez saisir le code à six chiffres suivant dans le formulaire Web pour confirmer que vous contrôlez cette adresse courriel.</td>
        </tr>

        <tr>
        <td colspan="3" align="center"><div style="font-size: 24px; background-color: red ;color: white;">&@STORE2FA</div></td>
        </tr>

        </table>

        </body>

        </html>
    % catégories de réponses
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    COLLECT2FA
    % texte de la question
      [EN]A six-digit code has been emailed to &&AEMAIL to confirm that you are the owner of this address. Please enter this six-digit code below.
      [FR]Un code à six chiffres a été envoyé par courriel à l'adresse &&AEMAIL pour confirmer que vous êtes bien propriétaire de cette adresse. Veuillez saisir ce code à six chiffres ci-bas.
    % texte de la note
    % catégories de réponses

    % sauts inconditionnels
    % condition
    % partie ouverte
      1 = C10 1 10
    ! ==================================================
    CODEOK CALCUL
    % texte de la question
      $CODEOK =
        {
        my $res = ( $ACOLLECT2FA eq $STORE2FA ) ? 1 : 0;
        $ACOLLECT2FA = "";
        $STORE2FA = "";
        return $res;
        }
    % texte de la note
    % catégories de réponses
      *0*[EN]Bad[FR]Mauvais
      *1*[EN]Good[FR]Bon
    % sauts inconditionnels
    % condition
    % partie ouverte
    ! ==================================================
    WRONGCODE BACKWALL
    % texte de la question
      [EN]The code you entered does not match the one emailed to &&AEMAIL. Do you want to get a new code?
      [FR]Le code que vous avez saisi ne correspond pas au code envoyé à &&AEMAIL. Voulez-vous recevoir un autre code?
    % texte de la note
    % catégories de réponses
      [EN]Yes[FR]Oui
      [EN]No[FR]Non
    % sauts inconditionnels
      1 = STORE2FA
    % condition
      CODEOK.EQ.0
    % partie ouverte
    ! ==================================================
    # URL = [EN]https://example.com/en/MAINproject/&&ATELKEY[FR]https://example.com/fr/MAINproject/&&ATELKEY
    QEND MIN=0 MAX=0 CULDESAC BACKWALL
    % texte de la question
      [EN]<p align="center">Thank you for your collaboration.<br />You may now close this window.</p>
      [FR]<p align="center">Merci pour votre collaboration.<br />Vous pouvez maintenant fermer cette fenêtre.</p>
    % texte de la note
    % catégories de réponses
    % sauts inconditionnels
    % condition
      EMAIL.EQ.2 .OR. WRONGCODE.EQ.2
    % partie ouverte
    ! ==================================================

Main project

The requirements of the main project are minimal.

  • it can be open if the designer wants to maintain the ability to access it directly; this kind of defeats the purpose though, so it is likely closed.
  • it contains a STOCK field to store the email address; this field is prepopulated with email addresses expected to fill out the questionnaire and, if so desired, it can accept new addresses supplied by the gatekeeper project.

   

E-mailing during the course of the questionnaire

Problem

You want to send an e-mail to the respondent during the course of the questionnaire.

Solution

    SEND_EMAIL CALCUL
    % Text
    OUTPUT_VARIABLE_NAME = email("from_e-mail_address","to_e-mail_address","MESSAGETEXT_VARIABLE_NAME")
    % Categories
    % Simple skips
    % Display condition
    % Open parts
    ! ===================================================<

Discussion

Using a CALCUL question, it is possible to send an e-mail in the course of completing the questionnaire. The syntax of such a question is suggested above.

The "from_e-mail_address" and the "to_e-mail_address" can be written out — in which case the at-sign must be accompanied by a backslash as in "info\@callweb.ca" (a Perl requirement). They could also be questionnaire fields. In this case, the CALCUL syntax would look like this:

    OUTPUT_VARIABLE_NAME = email($FROM_ADDRESS,$TO_ADDRESS,"MESSAGETEXT_VARIABLE_NAME")

The "MESSAGETEXT_VARIABLE_NAME" must be defined in the questionnaire and be of the EMAIL type. See the section on invitation e-mails for information on this question type.

After execution, the "OUTPUT_VARIABLE_NAME" contains the date and time when the message was sent or zero if the process was not successful.

E-mailing during the course of the questionnaire

Problem

You want to send an e-mail to the respondent during the course of the questionnaire.

Solution

    SEND_EMAIL CALCUL
    % Text
    OUTPUT_VARIABLE_NAME = email("from_e-mail_address","to_e-mail_address","MESSAGETEXT_VARIABLE_NAME")
    % Categories
    % Simple skips
    % Display condition
    % Open parts
    ! ===================================================<

Discussion

Using a CALCUL question, it is possible to send an e-mail in the course of completing the questionnaire. The syntax of such a question is suggested above.

The "from_e-mail_address" and the "to_e-mail_address" can be written out — in which case the at-sign must be accompanied by a backslash as in "info\@callweb.ca" (a Perl requirement). They could also be questionnaire fields. In this case, the CALCUL syntax would look like this:

    OUTPUT_VARIABLE_NAME = email($FROM_ADDRESS,$TO_ADDRESS,"MESSAGETEXT_VARIABLE_NAME")

The "MESSAGETEXT_VARIABLE_NAME" must be defined in the questionnaire and be of the EMAIL type. See the section on invitation e-mails for information on this question type.

After execution, the "OUTPUT_VARIABLE_NAME" contains the date and time when the message was sent or zero if the process was not successful.

   

Creating an open-end part allowing only unique values

Problem

You want to create an open-end part which accepts only values which have not yet been recorded in the same field.

Solution

The following Test pound instruction offers this service:

    # Test WhateverName =
      #> [TRIGGER]ANUMBER
      #> [CONDITION]{_nrecords_mysql("project","WHERE ANUMBER=\"$ANUMBER\" AND _telkey<>\"$_telkey\"") != 0}
      #> [MESSAGE][EN]This file number already exists[FR]Ce numéro de dossier existe déjà
      #> [TYPE]QUESTION

Discussion

Let's say one question requests a file number and that no two cases in the data base can refer to the same file number.

The Test instruction above is triggered by question ANUMBER before any data are recorded in the data base (thereby avoiding duplicate file number data). The trigger condition is key here:

  • the CONDITION is within braces, which indicates that it is in Perl format (rather than in xBase format);
  • the _nrecords_mysql function returns the number of records which correspond to a certain condition in a CallWeb data base;
  • its first argument is the name of the CallWeb project to query;
  • its second argument is the condition to test; here, it tests for the presence of ANUMBER values which are the same as the ANUMBER value just received from the user AND which are from cases other than the current record.

Creating an open-end part allowing only unique values

Problem

You want to create an open-end part which accepts only values which have not yet been recorded in the same field.

Solution

The following Test pound instruction offers this service:

    # Test WhateverName =
      #> [TRIGGER]ANUMBER
      #> [CONDITION]{_nrecords_mysql("project","WHERE ANUMBER=\"$ANUMBER\" AND _telkey<>\"$_telkey\"") != 0}
      #> [MESSAGE][EN]This file number already exists[FR]Ce numéro de dossier existe déjà
      #> [TYPE]QUESTION

Discussion

Let's say one question requests a file number and that no two cases in the data base can refer to the same file number.

The Test instruction above is triggered by question ANUMBER before any data are recorded in the data base (thereby avoiding duplicate file number data). The trigger condition is key here:

  • the CONDITION is within braces, which indicates that it is in Perl format (rather than in xBase format);
  • the _nrecords_mysql function returns the number of records which correspond to a certain condition in a CallWeb data base;
  • its first argument is the name of the CallWeb project to query;
  • its second argument is the condition to test; here, it tests for the presence of ANUMBER values which are the same as the ANUMBER value just received from the user AND which are from cases other than the current record.

   

Recording a Web completion as part of a dual-mode project

Problem

In the context of a dual-mode survey, you want self-completions done on the Web to be taken into consideration in CATI field management.

Solution

WEBCOMPLETED CALCUL
## This question is located where the questionnaire is considered complete
% Question
   _cetappel = &add_call_cetappel("WEB","Completed")
   ## The previous line MUST attribute the result to "_cetappel"
   ## The first argument is the name of the "interviewer" associated with the completed questionnaire.
   ## The second argument is the exact result code (see cwcodescati.cgi) to attribute to the "call".
% Note
% Categories
% Skips
% Condition
   # The line below must be adjusted to the IP address of CATI interviewers
   { $contexte{ip} !~ /192.168.1.1/ }
% Open part

! ####################################################################################################

Discussion

CallWeb projects set up for management in CATI context are controlled by a series of specialized modules. Telephone numbers are fed to interviewers, among other factors, on the basis of the completion status of the questionnaire. When questionnaires are available for completion via CATI and via the open Web, questionnaires completed without CATI assistance must be identified.

This identification takes the form of a fake telephone call entry added to the call history of a case that is completed over the Web. This addition is performed by a CALCUL question using the add_call_cetappel function; the result must imperatively be sent to the _cetappel variable. The CALCUL function must bear a display condition to be activated only when the questionnaire is completed over the Web; one way to do this is to exclude from this calculation all of the IP addresses specific to the CATI environment.

Recording a Web completion as part of a dual-mode project

Problem

In the context of a dual-mode survey, you want self-completions done on the Web to be taken into consideration in CATI field management.

Solution

WEBCOMPLETED CALCUL
## This question is located where the questionnaire is considered complete
% Question
   _cetappel = &add_call_cetappel("WEB","Completed")
   ## The previous line MUST attribute the result to "_cetappel"
   ## The first argument is the name of the "interviewer" associated with the completed questionnaire.
   ## The second argument is the exact result code (see cwcodescati.cgi) to attribute to the "call".
% Note
% Categories
% Skips
% Condition
   # The line below must be adjusted to the IP address of CATI interviewers
   { $contexte{ip} !~ /192.168.1.1/ }
% Open part

! ####################################################################################################

Discussion

CallWeb projects set up for management in CATI context are controlled by a series of specialized modules. Telephone numbers are fed to interviewers, among other factors, on the basis of the completion status of the questionnaire. When questionnaires are available for completion via CATI and via the open Web, questionnaires completed without CATI assistance must be identified.

This identification takes the form of a fake telephone call entry added to the call history of a case that is completed over the Web. This addition is performed by a CALCUL question using the add_call_cetappel function; the result must imperatively be sent to the _cetappel variable. The CALCUL function must bear a display condition to be activated only when the questionnaire is completed over the Web; one way to do this is to exclude from this calculation all of the IP addresses specific to the CATI environment.

   

Activating the CallWeb pretest mode and accepting respondent feedback

Problem

You want survey respondents to have the possibility to add comments about any question of the questionnaire.

Solution

Activate the CallWeb pretest mode using the PRETEST pound instruction.

Discussion

In pretest mode, CallWeb displays a hyperlink besides each question in the questionnaire. This hyperlink is to a one-page questionnaire called BASEpretest which querries the respondent or the interviewer about the nature and the content of the comment they want to leave on a particular question. The link contains the name of the project from which BASEpretest was called, the _telkey of the originating questionnaire and the name of the question from which BASEpretest was called. These pieces of information are stored along with the respondent feedback in the BASEpretest data base for further analysis.

The CallWeb pretest mode is activated when:

  • the PRETEST pound instruction contains text or HTML code;
  • the BASEpretest project has been compiled and the BASEpretest data base exists.

The content of the PRETEST pound instruction is the text or HTML code that will be hyperlinked by CallWeb. It is what the respondent sees in the questionnaire. For example, the instruction

    # PRETEST = [EN]<IMG SRC=gr/cwquestionnaire.gif BORDER=0>

displays a hyperlinked icon next to each question. Meanwhile, the instruction

    # PRETEST = [EN]Pretest

simply displays the word "Pretest" hyperlinked. Note that one PRETEST segment must exist for each language defined in the questionnaire, such as:

    # PRETEST = [EN]Pretest[FR]Prétest

This system can be used to collect interviewers' or respondents' comments during a pretest or respondents' comments during a more qualitative survey.

Activating the CallWeb pretest mode and accepting respondent feedback

Problem

You want survey respondents to have the possibility to add comments about any question of the questionnaire.

Solution

Activate the CallWeb pretest mode using the PRETEST pound instruction.

Discussion

In pretest mode, CallWeb displays a hyperlink besides each question in the questionnaire. This hyperlink is to a one-page questionnaire called BASEpretest which querries the respondent or the interviewer about the nature and the content of the comment they want to leave on a particular question. The link contains the name of the project from which BASEpretest was called, the _telkey of the originating questionnaire and the name of the question from which BASEpretest was called. These pieces of information are stored along with the respondent feedback in the BASEpretest data base for further analysis.

The CallWeb pretest mode is activated when:

  • the PRETEST pound instruction contains text or HTML code;
  • the BASEpretest project has been compiled and the BASEpretest data base exists.

The content of the PRETEST pound instruction is the text or HTML code that will be hyperlinked by CallWeb. It is what the respondent sees in the questionnaire. For example, the instruction

    # PRETEST = [EN]<IMG SRC=gr/cwquestionnaire.gif BORDER=0>

displays a hyperlinked icon next to each question. Meanwhile, the instruction

    # PRETEST = [EN]Pretest

simply displays the word "Pretest" hyperlinked. Note that one PRETEST segment must exist for each language defined in the questionnaire, such as:

    # PRETEST = [EN]Pretest[FR]Prétest

This system can be used to collect interviewers' or respondents' comments during a pretest or respondents' comments during a more qualitative survey.

   

Coding open-end questions

Problem

You want to code open-end questions into a numeric field.

Solution

Use cwnav.cgi along with the "Mass edit mode".

Discussion

The cwnav.cgi module displays a selection of cases (based on the criteria supplied) and a subset of questions from the questionnaire. Its "Mass edit" mode makes every field that is displayed editable.

To code open-end questions, follow these steps:

  • open cwnav.cgi in the project of interest;
  • enter the case selection criteria relevant to your situation; this could include selecting only cases which bear an open code on the question of interest.
  • select the close-ended and the open-end questions relevant to the coding task; this includes, at a minimum, the close-ended portion of the text to be coded and the corresponding open-end part, but also any other field which may be useful in understanding the open-end comment;
  • select the "Mass edit mode" checkbox;
    • do not select the "Closed lists as boxes" checkbox to obtain dropdown lists of available choices;
    • do select it if you want to get simple text boxes in which codes are manually entered;
  • click Action!

A new page is displayed with the cases and fields you selected. Any change made to the data are saved when the Action! button is clicked again.

Coding open-end questions

Problem

You want to code open-end questions into a numeric field.

Solution

Use cwnav.cgi along with the "Mass edit mode".

Discussion

The cwnav.cgi module displays a selection of cases (based on the criteria supplied) and a subset of questions from the questionnaire. Its "Mass edit" mode makes every field that is displayed editable.

To code open-end questions, follow these steps:

  • open cwnav.cgi in the project of interest;
  • enter the case selection criteria relevant to your situation; this could include selecting only cases which bear an open code on the question of interest.
  • select the close-ended and the open-end questions relevant to the coding task; this includes, at a minimum, the close-ended portion of the text to be coded and the corresponding open-end part, but also any other field which may be useful in understanding the open-end comment;
  • select the "Mass edit mode" checkbox;
    • do not select the "Closed lists as boxes" checkbox to obtain dropdown lists of available choices;
    • do select it if you want to get simple text boxes in which codes are manually entered;
  • click Action!

A new page is displayed with the cases and fields you selected. Any change made to the data are saved when the Action! button is clicked again.

   

Managing a do-not-call list in CATI mode

Problem

You want to use a list of telephone numbers that you never want called, in a CATI context.

Solution

Use BASEdonotcall to store the telephone numbers.

Discussion

If the BASEdonotcall project exists, CallWeb refuses to prepopulate cases corresponding to one of the telephone numbers found in that project; moreover, CallWeb does not dispatch numbers found in the do-not-call list to interviewers. This behaviour can be turned off on a project by project basis by adding the following pound instruction to the project questionnaire: # Use do not call list = no.

BASEdonotcall can store the following information:

  • a telephone number (in ATELEPHONE);
  • a source for the information (in SOURCE (see the BASEdonotlist .scw file for the meaning of each code); details on the source may be stored in ASOURCE);
  • the date of addition of the telephone number (in ADATE);
  • any other information you care to store (in INFO and AINFO).

Telephone numbers may be added to BASEdonotcall in three manners:

  • using callweb.cgi, BASEdonotcall can be brought up as a one-page questionnaire allowing the entry of the information listed above; this is how ad hoc additions to BASEdonotcall are made;
  • using cwprepop.cgi, BASEdonotcall can be populated with telephone numbers found in other sources (such as the ASDE cooperative do-not-call list);
  • interviewers can check a box on the case disposition screen to add a number to the list; if the "Do not call email" pound instruction is defined, a warning e-mail is sent to that address every time an interviewer adds a do-not-call number; BASEdonotcall also stores the name of the interviewer who added the number.

Managing a do-not-call list in CATI mode

Problem

You want to use a list of telephone numbers that you never want called, in a CATI context.

Solution

Use BASEdonotcall to store the telephone numbers.

Discussion

If the BASEdonotcall project exists, CallWeb refuses to prepopulate cases corresponding to one of the telephone numbers found in that project; moreover, CallWeb does not dispatch numbers found in the do-not-call list to interviewers. This behaviour can be turned off on a project by project basis by adding the following pound instruction to the project questionnaire: # Use do not call list = no.

BASEdonotcall can store the following information:

  • a telephone number (in ATELEPHONE);
  • a source for the information (in SOURCE (see the BASEdonotlist .scw file for the meaning of each code); details on the source may be stored in ASOURCE);
  • the date of addition of the telephone number (in ADATE);
  • any other information you care to store (in INFO and AINFO).

Telephone numbers may be added to BASEdonotcall in three manners:

  • using callweb.cgi, BASEdonotcall can be brought up as a one-page questionnaire allowing the entry of the information listed above; this is how ad hoc additions to BASEdonotcall are made;
  • using cwprepop.cgi, BASEdonotcall can be populated with telephone numbers found in other sources (such as the ASDE cooperative do-not-call list);
  • interviewers can check a box on the case disposition screen to add a number to the list; if the "Do not call email" pound instruction is defined, a warning e-mail is sent to that address every time an interviewer adds a do-not-call number; BASEdonotcall also stores the name of the interviewer who added the number.

   

Naming the sending e-mail address

Problem

You want to send mass, customized e-mail messages and have a real name appear in the From field.

Solution

Format the sending e-mail address of cwemail.cgi as follows: Company Name <email@company.com>

Discussion

If a simple e-mail address is used in the sending address field of cwemail.cgi, that simple address is displayed in the message From field at the receiving end. To have a more descriptive name appear in the From field instead, place that name left of the actual e-mail address and place "smaller than" and "greater than" signs on each side of the e-mail address.

Naming the sending e-mail address

Problem

You want to send mass, customized e-mail messages and have a real name appear in the From field.

Solution

Format the sending e-mail address of cwemail.cgi as follows: Company Name <email@company.com>

Discussion

If a simple e-mail address is used in the sending address field of cwemail.cgi, that simple address is displayed in the message From field at the receiving end. To have a more descriptive name appear in the From field instead, place that name left of the actual e-mail address and place "smaller than" and "greater than" signs on each side of the e-mail address.

   

Recording face-to-face interviews

Problem

You want to record face-to-face interviews using the computer microphone.

Solution

Create a CALCUL question to start and stop the recording using the record_wav function.

Discussion

To record face-to-face interviews (typically conducted using laptops or netbooks), the CallWeb computer needs:

  • a working microphone;
  • a working copy of the rec Linux program (which can be installed using any one of the Linux package managers);
  • the assurance that recording actually works from the terminal command line (which can be obtained by issuing a command such as rec -d test.wav at the command line to confirm that recording takes place).

In CallWeb, recording is initiated by a CALCUL question calling upon the record_wav function as in:

    AFILENAME = record_wav("start")

This stops any current recording, starts the recording and places the name of the recording file in AFILENAME. A second CALCUL question is used to stop recording. It also uses the record_wav function:

    PROCESSES = record_wav("stop")

The result of this call is to stop the recording and to return, in PROCESSES, the number of processes stopped.

The real difficulty in using this functionality is to give the Apache user the permission to start and stop recordings. This is done by adding lines such as the following ones to the /etc/sudoers file (assuming Apache runs under the "apache" user and that "someuser" has access to the rec and kill commands):

    apache ALL=(someuser) NOPASSWD:/usr/bin/rec
    apache ALL=(someuser) NOPASSWD:/bin/kill
    Defaults:apache !requiretty

Finally, add a line such as this one to the CallWeb usagerXXX.conf file; it contains the Linux prefix instructions necessary for the Apache user to issue the rec and kill commands):

    command_prefix_for_record_wav = sudo -u someuser

Recording face-to-face interviews

Problem

You want to record face-to-face interviews using the computer microphone.

Solution

Create a CALCUL question to start and stop the recording using the record_wav function.

Discussion

To record face-to-face interviews (typically conducted using laptops or netbooks), the CallWeb computer needs:

  • a working microphone;
  • a working copy of the rec Linux program (which can be installed using any one of the Linux package managers);
  • the assurance that recording actually works from the terminal command line (which can be obtained by issuing a command such as rec -d test.wav at the command line to confirm that recording takes place).

In CallWeb, recording is initiated by a CALCUL question calling upon the record_wav function as in:

    AFILENAME = record_wav("start")

This stops any current recording, starts the recording and places the name of the recording file in AFILENAME. A second CALCUL question is used to stop recording. It also uses the record_wav function:

    PROCESSES = record_wav("stop")

The result of this call is to stop the recording and to return, in PROCESSES, the number of processes stopped.

The real difficulty in using this functionality is to give the Apache user the permission to start and stop recordings. This is done by adding lines such as the following ones to the /etc/sudoers file (assuming Apache runs under the "apache" user and that "someuser" has access to the rec and kill commands):

    apache ALL=(someuser) NOPASSWD:/usr/bin/rec
    apache ALL=(someuser) NOPASSWD:/bin/kill
    Defaults:apache !requiretty

Finally, add a line such as this one to the CallWeb usagerXXX.conf file; it contains the Linux prefix instructions necessary for the Apache user to issue the rec and kill commands):

    command_prefix_for_record_wav = sudo -u someuser

   

Pushing data into a project during an interview

Problem

You want to place data or create a record in another project during an interview.

Solution

Create a CALCUL question using the put_values_in_case function.

Discussion

Say you need to store events in an event-based CallWeb data base and you don't want to use a RELATION question (for example, you want to store purchases made by the respondent the previous day). You need a way to ask a few questions, store these answers in another project (purchase-based) and loop back to the purchase questions. The looping back is performed by a skip, but the storing is trickier; the put_values_in_case function performs it.

The syntax of put_values_in_case is as follows:

    AFFECTED_TELKEY = put_values_in_case(
      _proj => "some_project",
      _telkey => "some_telkey",
      AQ1 => $AQMx,
      Q3 => $QNy,
      Q10 => $QOz
      );

The function put_values_in_case places the content of AQMx in the AQ1 field of the record identified by the _telkey "some_telkey" of project "some_project", as well as the content of QNy in Q3 and the content of QOz in Q10. There can be any number of such transfers in a single use of the function.The destination project is specified in the "_proj" key. Note that the function uses the Perl attribution operator (=>), NOT the equal sign.

If the data needs to be pushed into a particular record of that project, the _telkey of that record must be specified in the _telkey key. If no _telkey key is specified, put_values_in_case creates a new record in the destination project. If the destination _telkey is specified but does not exist, the function creates a record with that _telkey in the destination project. If a record needs to be created, the new _telkey contains 24 random letters and numbers unless a _pattern option was passed to the function; the _pattern option follows the "# Telkey pattern" syntax.

put_values_in_case returns the _telkey that was affected (i.e., the existing _telkey that was updated or the _telkey of the record that was created).

Pushing data into a project during an interview

Problem

You want to place data or create a record in another project during an interview.

Solution

Create a CALCUL question using the put_values_in_case function.

Discussion

Say you need to store events in an event-based CallWeb data base and you don't want to use a RELATION question (for example, you want to store purchases made by the respondent the previous day). You need a way to ask a few questions, store these answers in another project (purchase-based) and loop back to the purchase questions. The looping back is performed by a skip, but the storing is trickier; the put_values_in_case function performs it.

The syntax of put_values_in_case is as follows:

    AFFECTED_TELKEY = put_values_in_case(
      _proj => "some_project",
      _telkey => "some_telkey",
      AQ1 => $AQMx,
      Q3 => $QNy,
      Q10 => $QOz
      );

The function put_values_in_case places the content of AQMx in the AQ1 field of the record identified by the _telkey "some_telkey" of project "some_project", as well as the content of QNy in Q3 and the content of QOz in Q10. There can be any number of such transfers in a single use of the function.The destination project is specified in the "_proj" key. Note that the function uses the Perl attribution operator (=>), NOT the equal sign.

If the data needs to be pushed into a particular record of that project, the _telkey of that record must be specified in the _telkey key. If no _telkey key is specified, put_values_in_case creates a new record in the destination project. If the destination _telkey is specified but does not exist, the function creates a record with that _telkey in the destination project. If a record needs to be created, the new _telkey contains 24 random letters and numbers unless a _pattern option was passed to the function; the _pattern option follows the "# Telkey pattern" syntax.

put_values_in_case returns the _telkey that was affected (i.e., the existing _telkey that was updated or the _telkey of the record that was created).

   

Identifying straightlining

Problem

You want to identify straightlining behaviour and you want to react to it.

Solution

Use the straightlined function in a Test pound instruction.

Discussion

Straightlining occurs when a survey respondent selects the same answer to a series of questions. Typically, this is seen as a way to speed through a questionnaire, not answering thoughtfully.

The straightlined function can test for this behavour and, coupled with the # TEST instruction, it can react to it. This function is used as follows:

    straightlined("Qn-Qm","QFLAG")

It returns a 1 if all answers from Qn to Qm are the same while disregarding empty answers (which would correspond to false display conditions, for example). QFLAG is an optional question name; if it is present two things happen:

  • first, a 1 is stored in that field if the test fails;
  • second, the test is not conducted if there is already a 1 in that field.

Couple this with a # TEST instruction and you get something like this:

    # Test WhateverName =
      #> [TRIGGER]Q1
      #> [CONDITION] { straightlined("Q1-Q10","QFLAG") }
      #> [MESSAGE]
      #> [EN]One answer to all questions? Click Next page again if this is what you want to do or revise your answers.
      #> [FR]Une réponse à toutes ces questions? Cliquez sur Page suivante si c'est ce que vous vouluez ou révisez vos réponses.
      #> [TYPE]TABLE

That will display the error message and the matrix (presumably) if the respondent straightlined Q1 to Q10. If the respondent moves on without changing their answers, CallWeb accepts the straightlined answers because QFLAG contains a 1, thanks to the first test performed.

Identifying straightlining

Problem

You want to identify straightlining behaviour and you want to react to it.

Solution

Use the straightlined function in a Test pound instruction.

Discussion

Straightlining occurs when a survey respondent selects the same answer to a series of questions. Typically, this is seen as a way to speed through a questionnaire, not answering thoughtfully.

The straightlined function can test for this behavour and, coupled with the # TEST instruction, it can react to it. This function is used as follows:

    straightlined("Qn-Qm","QFLAG")

It returns a 1 if all answers from Qn to Qm are the same while disregarding empty answers (which would correspond to false display conditions, for example). QFLAG is an optional question name; if it is present two things happen:

  • first, a 1 is stored in that field if the test fails;
  • second, the test is not conducted if there is already a 1 in that field.

Couple this with a # TEST instruction and you get something like this:

    # Test WhateverName =
      #> [TRIGGER]Q1
      #> [CONDITION] { straightlined("Q1-Q10","QFLAG") }
      #> [MESSAGE]
      #> [EN]One answer to all questions? Click Next page again if this is what you want to do or revise your answers.
      #> [FR]Une réponse à toutes ces questions? Cliquez sur Page suivante si c'est ce que vous vouluez ou révisez vos réponses.
      #> [TYPE]TABLE

That will display the error message and the matrix (presumably) if the respondent straightlined Q1 to Q10. If the respondent moves on without changing their answers, CallWeb accepts the straightlined answers because QFLAG contains a 1, thanks to the first test performed.

   

Preparing a large prepop

Problem

You need to prepop a large number of data fields. You want to simplify the task and reduce the risk of human error.

Solution

Use cwprepop.cgi to create a template of the questions to include in your .scw file based on the data to prepop.

Discussion

Let's say you received a data file of administrative data that need to be included in a survey data base — maybe to recall some administrative values in the questionnaire or to produce an integrated data base at the end of the data collection to ease the statistical analysis. There are dozens (or hundreds!) of data fields, some open-ended, some simply categorised. There are alphanumeric fields and numeric fields, and among the latter, there are integer fields and real values. What a mess! It is going to take hours to craft the CallWeb questions to properly prepop all of these data, and there is a clear risk that some data will be given an incorrect data type.

Call cwprepop.cgi to the rescue. From the integrated module (cw.cgi), cwprepop.cgi can be instructed to "create an scw from a tab-delimited". Here is what you have to do:

  • insert a line at the very top of the data file and add the name of the fields for each column; make sure to name open-ended parts where the data contain information other than integers;
  • save the data as a tab-delimited file if it is not already in this format; from Excel, this is as simple as copying the whole sheet, and pasting it in a text editor;
  • upload the tab-delimited file to the CallWeb project directory;
  • call cwprepop.cgi with the "create an scw from a tab-delimited" option selected and using the tab-delimited file selected in the drop-down list.

Here is what CallWeb will do:

  • determine whether the data are numeric or alphanumeric;
  • calculate the width of the prepop fields and the number of decimals for real numbers;
  • validate the data in close-ended parts; they must be integers;
  • create an .scw file containing the definitions of questions required to read the data and to export them to optimal widths;
  • name the .scw file based on the tab-delimited file name and leave it in the project directory;
  • print a tabular report indicating which fields were defined, using which data type and which width.

Review the .scw file that is produced to make sure that CallWeb's assumptions and observations are reasonable in your own situation.

Preparing a large prepop

Problem

You need to prepop a large number of data fields. You want to simplify the task and reduce the risk of human error.

Solution

Use cwprepop.cgi to create a template of the questions to include in your .scw file based on the data to prepop.

Discussion

Let's say you received a data file of administrative data that need to be included in a survey data base — maybe to recall some administrative values in the questionnaire or to produce an integrated data base at the end of the data collection to ease the statistical analysis. There are dozens (or hundreds!) of data fields, some open-ended, some simply categorised. There are alphanumeric fields and numeric fields, and among the latter, there are integer fields and real values. What a mess! It is going to take hours to craft the CallWeb questions to properly prepop all of these data, and there is a clear risk that some data will be given an incorrect data type.

Call cwprepop.cgi to the rescue. From the integrated module (cw.cgi), cwprepop.cgi can be instructed to "create an scw from a tab-delimited". Here is what you have to do:

  • insert a line at the very top of the data file and add the name of the fields for each column; make sure to name open-ended parts where the data contain information other than integers;
  • save the data as a tab-delimited file if it is not already in this format; from Excel, this is as simple as copying the whole sheet, and pasting it in a text editor;
  • upload the tab-delimited file to the CallWeb project directory;
  • call cwprepop.cgi with the "create an scw from a tab-delimited" option selected and using the tab-delimited file selected in the drop-down list.

Here is what CallWeb will do:

  • determine whether the data are numeric or alphanumeric;
  • calculate the width of the prepop fields and the number of decimals for real numbers;
  • validate the data in close-ended parts; they must be integers;
  • create an .scw file containing the definitions of questions required to read the data and to export them to optimal widths;
  • name the .scw file based on the tab-delimited file name and leave it in the project directory;
  • print a tabular report indicating which fields were defined, using which data type and which width.

Review the .scw file that is produced to make sure that CallWeb's assumptions and observations are reasonable in your own situation.

   

Commenting a questionnaire

Problem

You need add comments to the questionnaire but you want to hide them easily.

Solution

Use a custom CSS style and the CSS "display" property.

Discussion

CallWeb includes the ability to place comments inside a script but some may find it less flexible than they would want.

You can also use CSS styles to your benefit in this case. For example, you can create a COMMENT style in the project style.css file (or other style sheet name) that could like like this:

    .COMMENT
    {
    background-color: #EEEEEE;
    color: #CC5C1C;
    font-weight: normal;
    display: block;
    border: 1px #A5A79A solid;
    padding-left: 2px;
    padding-top: 2px;
    padding-right: 2px;
    padding-bottom: 2px;
    }

Then, comments can be inserted in the questionnaire using SPAN tags, such as:

    <span class="COMMENT">Insert some comment here.</span>

This comment will appear in a text box formatted across the page when the questionnaire is displayed, including in Print mode. When the comments are no longer necessary, simply change the COMMENT style from display: block; to display: none; and comments will magically disappear.

Commenting a questionnaire

Problem

You need add comments to the questionnaire but you want to hide them easily.

Solution

Use a custom CSS style and the CSS "display" property.

Discussion

CallWeb includes the ability to place comments inside a script but some may find it less flexible than they would want.

You can also use CSS styles to your benefit in this case. For example, you can create a COMMENT style in the project style.css file (or other style sheet name) that could like like this:

    .COMMENT
    {
    background-color: #EEEEEE;
    color: #CC5C1C;
    font-weight: normal;
    display: block;
    border: 1px #A5A79A solid;
    padding-left: 2px;
    padding-top: 2px;
    padding-right: 2px;
    padding-bottom: 2px;
    }

Then, comments can be inserted in the questionnaire using SPAN tags, such as:

    <span class="COMMENT">Insert some comment here.</span>

This comment will appear in a text box formatted across the page when the questionnaire is displayed, including in Print mode. When the comments are no longer necessary, simply change the COMMENT style from display: block; to display: none; and comments will magically disappear.