Settings Page
A Custom Question’s custom settings page is available in Survey Designer (Custom Settings tab on Question view). Settings will be stored in a json blob field in a survey schema corresponding to the specific question, so it can be an object of any structure.
The Settings page is optional; if you don’t have any custom settings you would like to edit in SD then you don’t need it. In Runtime you already have access to lots of base question settings, so create a Settings page if you need additional settings that are not available in the base question type. |
The Design folder created by the npx tool or CQ client app wizard contains the javascript file custom-question.js. This file is essential to make cross-domain communication between the settings page and Survey Desginer work. Do not modify or delete it.
The Settings page is a static html page embedded in SD in the sandboxed iframe. It does not have the ability to access any data from SD directly. Use callbacks (described below) to get read-only settings from SD (the data is posted via postMessages for cross frame communication).
API
The Settings page should reference custom-question.js, and implement two lifecycle methods:
-
customQuestion.onInit(customQuestionSettings, uiSettings, questionSettings, projectSettings) - to be able to receive contextual information from Survey Designer (will be executed only once - on page load).
-
customQuestion.onSettingsReceived(customQuestionSettings, uiSettings) - to be able to receive properties from Survey Designer (will be executed every time CQ settings or SD UI settings change in SD).
The page must also call customQuestion.saveChanges(settings, hasError) to notify Survey Designer about changes made. The Boolean flag hasError tells Survey Designer that the current state is invalid, so it will display the "changes made" status, but will not actually save them until the error is fixed. It is a useful feature if you want to implement custom settings validation.
onSettingsReceived is fired every time customQuestionSettings is changed in SD (for example, when the user performs Undo). This means it will also fire on every saveChanges call handled by SD. If you do not want to update your models with up-to-date data from SD then use local state and synchronize it manually. |
Best Practice: keep the js file separate from html markup and use transpilation - this will allow you to use modern js syntax and not worry about legacy browsers support. |
Models
Name | Model | Example |
---|---|---|
customQuestionSettings |
CQ settings object (the object you pass to the customQuestion.saveChanges call), can be any structure this object will be available as-is in CQ runtime |
\{ myValue: 1, myAnotherValue: \{ width: "64px", height: "12px" \} \} |
uiSettings |
SD UI settings currentLanguage is current 'selected' language - changed when user select another language in question details dropdown readOnly is set to true when user don’t have enough permissions to edit question. you can disable your html inputs when it’s read-only for better UX |
\{ currentLanguage: 9, readOnly: false \} |
questionSettings |
Base question settings from survey schema answers (if available) scales (if available) data returned as it’s stored in survey schema: * table lookup answers won’t be expanded * predefined answers will be expanded * answer texts may be missing for specific languages (if not specified in schema) * answer types available: 'Answer', 'LoopLevelReference', 'HeaderAnswer', 'HeaderAnswerEnd' |
\{ answers: [ code: 'a', type: 'Answer', texts: [ \{ language: 9, text: 'My answer' \}, \{ language: 25, text: 'Мой ответ' \} ] ], scales: [ code: 'b', type: 'Answer', texts: [ \{ language: 9, text: 'My scale answer' \} ] ] \} |
projectSettings |
Survey (project) settings languages - available survey languages with names |
\{ languages: [ \{ id: 9, name: 'English' \}, \{ id: 25, name: 'Russian' \} ] \} |
Dependencies
You can add dependencies to local or external resources to your html page. When adding local dependencies use relative paths. All files in the CQ bundle are available via FileLibrary. For example:
<-- local dependency example: myscript.js file is in the same folder as html page -->
<script src="myscript.js"></script>
<-- external dependency example -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
Settings page dependencies are declared inside the html page, Runtime dependencies are in metadata.json - they are not shared. |
Examples
Basic settings page with a single input with custom question settings \{ myProp: value \}.
onInit callback is not used here - if you do not need access to questionSettings or projectSettings then onSettingsReceived is all you need.
<!DOCTYPE html>
<html>
<head>
<script src="custom-question.js"></script>
</head>
<body>
<div>
<input type="text" id="my-prop" oninput="saveChanges()"/>
</div>
<script>
var input = document.getElementById('my-prop');
function onSettingsReceived(settings, uiSettings) {
input.value = settings.myProp;
}
function saveChanges() {
var settings = {
myProp: input.value
};
var hasError = !settings.myProp;
customQuestion.saveChanges(settings, hasError);
}
customQuestion.onChange = onSettingsReceived;
</script>
</body>
</html>
It is recommended to use the onInput event to handle input changes, not onChange, as onInput will fire saveChanges earlier so it will be tracked by the SD save handler. |
The following example relates to multi language support. The Language caption on the page is updated only when the language is changed in the SurveyDesigner (it is not updated when other settings are changed).
<!DOCTYPE html>
<html>
<head>
<script src="custom-question.js"></script>
</head>
<body>
<div>
<h1 id="lang"></h1>
<input type="text" id="my-prop" oninput="saveChanges()"/>
</div>
<script>
var localState = {
languages: [],
currentLanguage: 0
}
var input = document.getElementById('my-prop');
var caption = document.getElementById('lang');
function setLabels() {
var lang = localState.languages.find(function (language) {
return language.id == localState.currentLanguage
});
caption.innerText = lang;
}
function setInputValues(settings) {
input.value = settings.myProp;
}
function onInit(settings, uiSettings, questionSettings, projectSettings) {
localState = {
languages: projectSettings.languages,
currentLanguage: uiSettings.currentLanguage
}
setLabels();
setInputValues(settings);
}
function onSettingsReceived(settings, uiSettings) {
if (localState.currentLanguage !== uiSettings.currentLanguage) {
localState.currentLanguage = uiSettings.currentLanguage;
setLabels();
}
setInputValues(settings);
}
function saveChanges() {
var settings = {
myProp: input.value
};
var hasError = !settings.myProp;
customQuestion.saveChanges(settings, hasError);
}
customQuestion.onInit = onInit;
customQuestion.onChange = onSettingsReceived;
</script>
</body>
</html>