Question Types & Handling
Forsta HX Platform - API Scripting Guide
Question Types & Handling
Grid/Matrix Questions:
// Handle grid questions (multiple rows/cols)
// Format: AQ1_r1 (row 1), AQ1_r2 (row 2), etc.
/**
* getGridResponses - Collects all responses from a grid/matrix question
* @param {string} baseQid - The base question ID prefix (e.g., 'AQ1')
* @returns {Object} - Object with row IDs as keys and values as responses
* @description Finds all elements matching the grid pattern (baseQid_r*)
* and builds an object containing all row responses.
*/
function getGridResponses(baseQid) {
var responses = {}; // Object to store row ID -> value pairs
$('[id^="' + baseQid + '_r"]').each(function() {
var rowId = $(this).attr('id');
responses[rowId] = $(this).val();
});
return responses;
}
/**
* validateGrid - Validates that a minimum number of grid rows are completed
* @param {string} baseQid - The base question ID prefix (e.g., 'AQ1')
* @param {number} requiredRows - Minimum number of rows that must have values
* @returns {boolean} - Returns true if enough rows are completed
* @description Gets all grid responses and counts how many have non-empty values.
* Compares against the required minimum threshold.
*/
function validateGrid(baseQid, requiredRows) {
// Get all responses from the grid question
var responses = getGridResponses(baseQid);
var completed = Object.keys(responses).filter(function(key) {
return responses[key] && responses[key] !== '';
}).length;
return completed >= requiredRows;
}
// Usage
if(!validateGrid('AQ1', 5)) {
alert('Please complete all 5 rows');
}
Rank Order Questions:
/**
* getRankOrder - Extracts the current ranking order from a drag-and-drop question
* @param {string} qid - The question ID containing ranked items
* @returns {Array} - Array of objects with position, value, and label for each item
* @description Iterates through ranked items and builds an array of rank data.
* Each object contains the item's position (1-indexed), data value,
* and display label text.
*/
function getRankOrder(qid) {
var ranks = []; // Array to store rank position objects
f(qid).find('.rank-item').each(function(index) {
ranks.push({
position: index + 1,
value: $(this).data('value'),
label: $(this).text()
});
});
return ranks;
}
/**
* validateRanking - Validates that no duplicate ranks are assigned
* @param {string} qid - The ranking question ID to validate
* @returns {boolean} - Returns true if all ranks are unique, false if duplicates found
* @description Collects all selected rank values from dropdown selects,
* creates a Set to find unique values, and compares lengths.
* If lengths differ, duplicates exist.
*/
function validateRanking(qid) {
// Get all selected values from rank dropdowns using jQuery .map()
var values = f(qid).find('select').map(function() {
return $(this).val(); // Get each dropdown's selected value
}).get(); // Convert jQuery object to plain array
var unique = [...new Set(values)];
if(unique.length !== values.length) {
alert('Please ensure each rank is unique');
return false;
}
return true;
}
Open-End Text Processing:
/**
* countWords - Counts the number of words in a text string
* @param {string} text - The text to count words in
* @returns {number} - The number of words found
* @description Trims whitespace, splits on whitespace characters (\s+),
* filters out empty strings, and returns the count.
* Used for minimum word count validation on open-ends.
*/
function countWords(text) {
// .trim() removes leading/trailing whitespace
// .split(/\s+/) splits on one or more whitespace chars
// .filter() removes any empty strings from the array
return text.trim().split(/\s+/).filter(function(word) {
return word.length > 0;
}).length;
}
// Validate minimum word count
f(AQ1).on('blur', function() {
var text = $(this).val();
var wordCount = countWords(text);
if(wordCount < 10) {
$(this).addClass('error');
alert('Please provide at least 10 words');
} else {
$(this).removeClass('error');
}
});
/**
* bannedWords - Array of words to filter from open-end responses
* @type {Array}
* @description List of prohibited words for basic profanity/spam detection.
* Expand this array based on your specific filtering needs.
*/
var bannedWords = ['spam', 'test', 'asdf'];
/**
* containsProfanity - Checks if text contains any banned words
* @param {string} text - The text to check for banned words
* @returns {boolean} - Returns true if any banned word is found
* @description Converts text to lowercase for case-insensitive matching,
* then uses .some() to check if any banned word exists.
* Returns true on first match found.
*/
function containsProfanity(text) {
var lower = text.toLowerCase(); // Case-insensitive comparison
// .some() returns true if callback returns true for any element
return bannedWords.some(function(word) {
return lower.includes(word); // Check if banned word exists in text
});
}