survey-core
Version:
survey.js is a JavaScript Survey Library. It is a modern way to add a survey to your website. It uses JSON for survey metadata and results.
1,409 lines (1,404 loc) • 2.3 MB
JavaScript
/*!
* surveyjs - Survey JavaScript library v2.0.4
* Copyright (c) 2015-2025 Devsoft Baltic OÜ - http://surveyjs.io/
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
/* eslint-disable no-restricted-globals */
class DomWindowHelper {
static isAvailable() {
return "undefined" !== typeof window;
}
static isFileReaderAvailable() {
if (!DomWindowHelper.isAvailable())
return false;
return !!window["FileReader"];
}
static getLocation() {
if (!DomWindowHelper.isAvailable())
return;
return window.location;
}
static getVisualViewport() {
if (!DomWindowHelper.isAvailable())
return null;
return window.visualViewport;
}
static getInnerWidth() {
if (!DomWindowHelper.isAvailable())
return;
return window.innerWidth;
}
static getInnerHeight() {
if (!DomWindowHelper.isAvailable())
return null;
return window.innerHeight;
}
static getDevicePixelRatio() {
if (!DomWindowHelper.isAvailable())
return null;
return window.devicePixelRatio;
}
static getWindow() {
if (!DomWindowHelper.isAvailable())
return;
return window;
}
static hasOwn(propertyName) {
if (!DomWindowHelper.isAvailable())
return;
return propertyName in window;
}
static getSelection() {
if (DomWindowHelper.isAvailable() && window.getSelection) {
return window.getSelection();
}
}
static requestAnimationFrame(callback) {
if (DomWindowHelper.isAvailable()) {
return window.requestAnimationFrame(callback);
}
}
static addEventListener(type, listener) {
if (!DomWindowHelper.isAvailable())
return;
window.addEventListener(type, listener);
}
static removeEventListener(type, listener) {
if (!DomWindowHelper.isAvailable())
return;
window.removeEventListener(type, listener);
}
static matchMedia(mediaQueryString) {
if (!DomWindowHelper.isAvailable() || typeof window.matchMedia === "undefined")
return null;
return window.matchMedia(mediaQueryString);
}
}
class DomDocumentHelper {
static isAvailable() {
return "undefined" !== typeof document;
}
static getBody() {
if (!DomDocumentHelper.isAvailable())
return;
return document.body;
}
static getDocumentElement() {
if (!DomDocumentHelper.isAvailable())
return;
return document.documentElement;
}
static getDocument() {
if (!DomDocumentHelper.isAvailable())
return;
return document;
}
static getCookie() {
if (!DomDocumentHelper.isAvailable())
return;
return document.cookie;
}
static setCookie(newCookie) {
if (!DomDocumentHelper.isAvailable())
return;
document.cookie = newCookie;
}
static activeElementBlur() {
if (!DomDocumentHelper.isAvailable())
return;
const activeElement = document.activeElement;
if (!!activeElement && !!activeElement.blur) {
activeElement.blur();
}
}
static createElement(tagName) {
if (!DomDocumentHelper.isAvailable())
return;
return document.createElement(tagName);
}
static getComputedStyle(elt) {
if (!DomDocumentHelper.isAvailable())
return new CSSStyleDeclaration();
return document.defaultView.getComputedStyle(elt);
}
static addEventListener(type, listener) {
if (!DomDocumentHelper.isAvailable())
return;
document.addEventListener(type, listener);
}
static removeEventListener(type, listener) {
if (!DomDocumentHelper.isAvailable())
return;
document.removeEventListener(type, listener);
}
}
function createDate(reason, val) {
if (!val)
return new Date();
if (!settings.storeUtcDates && typeof val === "string" && isISODateOnly(val)) {
val += "T00:00:00";
}
const d = new Date(val);
return settings.onDateCreated(d, reason, val);
}
function isISODateOnly(str) {
if (str.indexOf("T") > 0)
return false;
if (!/\d{4}-\d{2}-\d{2}/.test(str))
return false;
return !isNaN(new Date(str).getTime());
}
class Helpers {
/**
* A static methods that returns true if a value undefined, null, empty string or empty array.
* @param value
*/
static isValueEmpty(value) {
if (Array.isArray(value) && value.length === 0)
return true;
if (!!value && Helpers.isValueObject(value) && value.constructor === Object) {
for (var key in value) {
if (!Helpers.isValueEmpty(value[key]))
return false;
}
return true;
}
return !value && value !== 0 && value !== false;
}
static isValueUndefined(value) {
return (value === undefined || value === null) || (typeof value === "number" && isNaN(value));
}
static isArrayContainsEqual(x, y) {
if (!Array.isArray(x) || !Array.isArray(y))
return false;
if (x.length !== y.length)
return false;
for (var i = 0; i < x.length; i++) {
var j = 0;
for (; j < y.length; j++) {
if (Helpers.isTwoValueEquals(x[i], y[j]))
break;
}
if (j === y.length)
return false;
}
return true;
}
static checkIfArraysEqual(x, y, params) {
if (!Array.isArray(x) || !Array.isArray(y))
return false;
if (x.length !== y.length)
return false;
const ignoreOrder = params.ignoreOrder !== undefined ? params.ignoreOrder : false;
if (ignoreOrder) {
var xSorted = [];
var ySorted = [];
for (var i = 0; i < x.length; i++) {
xSorted.push(x[i]);
ySorted.push(y[i]);
}
xSorted.sort();
ySorted.sort();
x = xSorted;
y = ySorted;
}
for (var i = 0; i < x.length; i++) {
if (!Helpers.checkIfValuesEqual(x[i], y[i], params))
return false;
}
return true;
}
static isArraysEqual(x, y, ignoreOrder = false, caseSensitive, trimStrings) {
return Helpers.checkIfArraysEqual(x, y, { ignoreOrder: ignoreOrder, caseSensitive: caseSensitive, trimStrings: trimStrings });
}
static compareStrings(x, y) {
const normalize = settings.comparator.normalizeTextCallback;
if (!!x)
x = normalize(x, "compare").trim();
if (!!y)
y = normalize(y, "compare").trim();
if (!x && !y)
return 0;
if (!x)
return -1;
if (!y)
return 1;
if (x === y)
return 0;
let digitIndex = -1;
for (let i = 0; i < x.length && i < y.length; i++) {
if (this.isCharDigit(x[i]) && this.isCharDigit(y[i])) {
digitIndex = i;
break;
}
if (x[i] !== y[i])
break;
}
if (digitIndex > -1) {
let nX = this.getNumberFromStr(x, digitIndex);
let nY = this.getNumberFromStr(y, digitIndex);
if (!Number.isNaN(nX) && !Number.isNaN(nY) && nX !== nY) {
return nX > nY ? 1 : -1;
}
}
return x > y ? 1 : -1;
}
static checkIfValuesEqual(x, y, params) {
if (x === y)
return true;
if (Array.isArray(x) && x.length === 0 && typeof y === "undefined")
return true;
if (Array.isArray(y) && y.length === 0 && typeof x === "undefined")
return true;
if ((x === undefined || x === null) && y === "")
return true;
if ((y === undefined || y === null) && x === "")
return true;
const caseSensitive = params.caseSensitive !== undefined ? params.caseSensitive : settings.comparator.caseSensitive;
const trimStrings = params.trimStrings !== undefined ? params.trimStrings : settings.comparator.trimStrings;
if (typeof x === "string" && typeof y === "string") {
const normalize = settings.comparator.normalizeTextCallback;
x = normalize(x, "compare");
y = normalize(y, "compare");
if (trimStrings) {
x = x.trim();
y = y.trim();
}
if (!caseSensitive) {
x = x.toLowerCase();
y = y.toLowerCase();
}
return x === y;
}
if (x instanceof Date && y instanceof Date)
return x.getTime() == y.getTime();
const convertNumbers = !params.doNotConvertNumbers;
if (convertNumbers && Helpers.isConvertibleToNumber(x) && Helpers.isConvertibleToNumber(y)) {
if (parseInt(x) === parseInt(y) && parseFloat(x) === parseFloat(y)) {
return true;
}
}
if ((!Helpers.isValueEmpty(x) && Helpers.isValueEmpty(y)) ||
(Helpers.isValueEmpty(x) && !Helpers.isValueEmpty(y)))
return false;
if ((x === true || x === false) && typeof y == "string") {
return x.toString() === y.toLocaleLowerCase();
}
if ((y === true || y === false) && typeof x == "string") {
return y.toString() === x.toLocaleLowerCase();
}
const isXObj = Helpers.isValueObject(x);
const isYObj = Helpers.isValueObject(y);
if (!isXObj && !isYObj && (convertNumbers || (typeof x !== "number" && typeof y !== "number")))
return x == y;
if (!isXObj || !isYObj)
return false;
if (x["equals"] && y["equals"])
return x.equals(y);
if (Array.isArray(x) && Array.isArray(y)) {
return Helpers.checkIfArraysEqual(x, y, params);
}
for (var p in x) {
if (!x.hasOwnProperty(p))
continue;
if (!y.hasOwnProperty(p))
return false;
if (!this.checkIfValuesEqual(x[p], y[p], params))
return false;
}
for (p in y) {
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p))
return false;
}
return true;
}
static isTwoValueEquals(x, y, ignoreOrder = false, caseSensitive, trimStrings) {
return this.checkIfValuesEqual(x, y, { ignoreOrder: ignoreOrder, caseSensitive: caseSensitive, trimStrings: trimStrings });
}
static randomizeArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
static getUnbindValue(value) {
if (Array.isArray(value)) {
const res = [];
for (let i = 0; i < value.length; i++) {
res.push(Helpers.getUnbindValue(value[i]));
}
return res;
}
if (!!value && Helpers.isValueObject(value) && !(value instanceof Date)) {
return JSON.parse(JSON.stringify(value));
}
return value;
}
static createCopy(obj) {
var res = {};
if (!obj)
return res;
for (var key in obj) {
res[key] = obj[key];
}
return res;
}
static isConvertibleToNumber(value) {
return (value !== undefined &&
value !== null &&
!Array.isArray(value) &&
!isNaN(value));
}
static isValueObject(val, excludeArray) {
return val instanceof Object && (!excludeArray || !Array.isArray(val));
}
static isNumber(value) {
return !isNaN(this.getNumber(value));
}
static getNumber(value) {
const newValue = Helpers.getNumberCore(value);
return settings.parseNumber(value, newValue);
}
static getNumberCore(value) {
if (typeof value == "string") {
value = value.trim();
if (!value)
return NaN;
if (value.indexOf("0x") == 0) {
if (value.length > 32)
return NaN;
return parseInt(value);
}
if (value.length > 15 && Helpers.isDigitsOnly(value))
return NaN;
if (Helpers.isStringHasOperator(value))
return NaN;
}
value = this.prepareStringToNumber(value);
const res = parseFloat(value);
if (isNaN(res) || !isFinite(value))
return NaN;
return res;
}
static isStringHasOperator(str) {
if (str.lastIndexOf("-") > 0)
return false;
if (str.lastIndexOf("+") > 0)
return false;
const operators = "*^/%";
for (let i = 0; i < operators.length; i++) {
if (str.indexOf(operators[i]) > -1)
return true;
}
return false;
}
static prepareStringToNumber(val) {
if (typeof val !== "string" || !val)
return val;
let i = val.indexOf(",");
if (i > -1 && val.indexOf(",", i + 1) < 0) {
return val.replace(",", ".");
}
return val;
}
static getMaxLength(maxLength, surveyLength) {
if (maxLength < 0) {
maxLength = surveyLength;
}
return maxLength > 0 ? maxLength : null;
}
static getRemainingCharacterCounterText(newValue, maxLength) {
if (!maxLength || maxLength <= 0 || !settings.showMaxLengthIndicator) {
return "";
}
const value = newValue ? newValue.length : "0";
return [value, maxLength].join("/");
}
static getNumberByIndex(index, startIndexStr, parentIndex) {
if (index < 0)
return "";
var startIndex = 1;
var prefix = "";
var postfix = ".";
var isNumeric = true;
var strIndex = "A";
var str = "";
const hasDigitFunc = (str) => {
if (!str)
return false;
for (var i = 0; i < str.length; i++) {
if (Helpers.isCharDigit(str[i]))
return true;
}
return false;
};
if (!!startIndexStr) {
str = startIndexStr;
var ind = str.length - 1;
var hasDigit = hasDigitFunc(str);
var checkLetter = function () {
return ((hasDigit && !Helpers.isCharDigit(str[ind])) ||
Helpers.isCharNotLetterAndDigit(str[ind]));
};
while (ind >= 0 && checkLetter())
ind--;
var newPostfix = "";
if (ind < str.length - 1) {
newPostfix = str.substring(ind + 1);
str = str.substring(0, ind + 1);
}
ind = str.length - 1;
while (ind >= 0) {
if (checkLetter())
break;
ind--;
if (!hasDigit)
break;
}
strIndex = str.substring(ind + 1);
prefix = str.substring(0, ind + 1);
if (parseInt(strIndex))
startIndex = parseInt(strIndex);
else if (strIndex.length == 1)
isNumeric = false;
if (!!newPostfix || !!prefix) {
postfix = newPostfix;
}
}
if (parentIndex > -1 && hasDigitFunc(prefix)) {
prefix = this.getNumberByIndex(parentIndex, prefix);
}
if (isNumeric) {
let val = (index + startIndex).toString();
while (val.length < strIndex.length)
val = "0" + val;
return prefix + val + postfix;
}
return (prefix + String.fromCharCode(strIndex.charCodeAt(0) + index) + postfix);
}
static isCharNotLetterAndDigit(ch) {
return ch.toUpperCase() == ch.toLowerCase() && !Helpers.isCharDigit(ch);
}
static isCharDigit(ch) {
return ch >= "0" && ch <= "9";
}
static isDigitsOnly(str) {
if (!str)
return false;
for (let i = 0; i < str.length; i++) {
if (!Helpers.isCharDigit(str[i]))
return false;
}
return true;
}
static getNumberFromStr(str, index) {
if (!this.isCharDigit(str[index]))
return NaN;
let nStr = "";
while (index < str.length && this.isCharDigit(str[index])) {
nStr += str[index];
index++;
}
if (!nStr)
return NaN;
return this.getNumber(nStr);
}
static countDecimals(value) {
if (Helpers.isNumber(value) && Math.floor(value) !== value) {
const strs = value.toString().split(".");
return strs.length > 1 && strs[1].length || 0;
}
return 0;
}
static correctAfterPlusMinis(a, b, res) {
const digitsA = Helpers.countDecimals(a);
const digitsB = Helpers.countDecimals(b);
if (digitsA > 0 || digitsB > 0) {
const digits = Math.max(digitsA, digitsB);
res = parseFloat(res.toFixed(digits));
}
return res;
}
static sumAnyValues(a, b) {
if (!Helpers.isNumber(a) || !Helpers.isNumber(b)) {
if (Array.isArray(a) && Array.isArray(b))
return [].concat(a).concat(b);
if (Array.isArray(a) || Array.isArray(b)) {
const arr = Array.isArray(a) ? a : b;
const val = arr === a ? b : a;
if (typeof val === "string") {
const str = arr.join(", ");
return arr === a ? str + val : val + str;
}
if (typeof val === "number") {
let res = 0;
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] === "number") {
res = Helpers.correctAfterPlusMinis(res, arr[i], res + arr[i]);
}
}
return Helpers.correctAfterPlusMinis(res, val, res + val);
}
}
return a + b;
}
if (typeof a === "string" || typeof b === "string")
return a + b;
return Helpers.correctAfterPlusMinis(a, b, a + b);
}
static correctAfterMultiple(a, b, res) {
const digits = Helpers.countDecimals(a) + Helpers.countDecimals(b);
if (digits > 0) {
res = parseFloat(res.toFixed(digits));
}
return res;
}
static convertArrayValueToObject(src, propName, dest = undefined) {
const res = new Array();
if (!src || !Array.isArray(src))
return res;
for (var i = 0; i < src.length; i++) {
let item;
if (Array.isArray(dest)) {
item = Helpers.findObjByPropValue(dest, propName, src[i]);
}
if (!item) {
item = {};
item[propName] = src[i];
}
res.push(item);
}
return res;
}
static findObjByPropValue(arr, propName, val) {
for (var i = 0; i < arr.length; i++) {
if (Helpers.isTwoValueEquals(arr[i][propName], val))
return arr[i];
}
return undefined;
}
static convertArrayObjectToValue(src, propName) {
const res = new Array();
if (!src || !Array.isArray(src))
return res;
for (var i = 0; i < src.length; i++) {
const itemVal = !!src[i] ? src[i][propName] : undefined;
if (!Helpers.isValueEmpty(itemVal))
res.push(itemVal);
}
return res;
}
static convertDateToString(date) {
const toStr = (val) => {
if (val < 10)
return "0" + val.toString();
return val.toString();
};
return date.getFullYear() + "-" + toStr(date.getMonth() + 1) + "-" + toStr(date.getDate());
}
static convertDateTimeToString(date) {
const toStr = (val) => {
if (val < 10)
return "0" + val.toString();
return val.toString();
};
return this.convertDateToString(date) + " " + toStr(date.getHours()) + ":" + toStr(date.getMinutes());
}
static convertValToQuestionVal(val, inputType) {
if (val instanceof Date) {
if (inputType === "datetime-local")
return Helpers.convertDateTimeToString(val);
return Helpers.convertDateToString(val);
}
return this.getUnbindValue(val);
}
static compareVerions(ver1, ver2) {
if (!ver1 && !ver2)
return 0;
const ver1Ar = ver1.split(".");
const ver2Ar = ver2.split(".");
const len1 = ver1Ar.length;
const len2 = ver2Ar.length;
for (let i = 0; i < len1 && i < len2; i++) {
const str1 = ver1Ar[i];
const str2 = ver2Ar[i];
if (str1.length === str2.length) {
if (str1 !== str2) {
return str1 < str2 ? -1 : 1;
}
}
else {
return str1.length < str2.length ? -1 : 1;
}
}
return len1 === len2 ? 0 : (len1 < len2 ? -1 : 1);
}
static isUrlYoutubeVideo(url) {
if (!url)
return false;
const youtubeDomains = ["www.youtube.com", "m.youtube.com", "youtube.com", "youtu.be"];
url = url.toLowerCase();
url = url.replace(/^https?:\/\//, "");
for (let i = 0; i < youtubeDomains.length; i++) {
if (url.indexOf(youtubeDomains[i] + "/") === 0)
return true;
}
return false;
}
}
if (!String.prototype["format"]) {
String.prototype["format"] = function () {
var args = arguments;
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != "undefined" ? args[number] : match;
});
};
}
// Uncomment the line below if you create a custom dictionary
// import { setupLocale } from "survey-core";
var englishStrings = {
pagePrevText: "Previous",
pageNextText: "Next",
completeText: "Complete",
previewText: "Preview",
editText: "Edit",
startSurveyText: "Start",
otherItemText: "Other (describe)",
noneItemText: "None",
refuseItemText: "Refuse to answer",
dontKnowItemText: "Don't know",
selectAllItemText: "Select All",
deselectAllItemText: "Deselect all",
progressText: "Page {0} of {1}",
indexText: "{0} of {1}",
panelDynamicProgressText: "{0} of {1}",
panelDynamicTabTextFormat: "Panel {panelIndex}",
questionsProgressText: "Answered {0}/{1} questions",
emptySurvey: "The survey doesn't contain any visible elements.",
completingSurvey: "Thank you for completing the survey",
completingSurveyBefore: "You have already completed this survey.",
loadingSurvey: "Loading Survey...",
placeholder: "Select...",
ratingOptionsCaption: "Select...",
value: "value",
requiredError: "Response required.",
requiredErrorInPanel: "Response required: answer at least one question.",
requiredInAllRowsError: "Response required: answer questions in all rows.",
eachRowUniqueError: "Each row must have a unique value.",
numericError: "The value should be numeric.",
minError: "The value should not be less than {0}",
maxError: "The value should not be greater than {0}",
textNoDigitsAllow: "Numbers are not allowed.",
textMinLength: "Please enter at least {0} character(s).",
textMaxLength: "Please enter no more than {0} character(s).",
textMinMaxLength: "Please enter at least {0} and no more than {1} characters.",
minRowCountError: "Please fill in at least {0} row(s).",
minSelectError: "Please select at least {0} option(s).",
maxSelectError: "Please select no more than {0} option(s).",
numericMinMax: "The '{0}' should be at least {1} and at most {2}",
numericMin: "The '{0}' should be at least {1}",
numericMax: "The '{0}' should be at most {1}",
invalidEmail: "Please enter a valid e-mail address.",
invalidExpression: "The expression: {0} should return 'true'.",
urlRequestError: "The request returned error '{0}'. {1}",
urlGetChoicesError: "The request returned empty data or the 'path' property is incorrect",
exceedMaxSize: "The file size should not exceed {0}.",
noUploadFilesHandler: "Files cannot be uploaded. Please add a handler for the 'onUploadFiles' event.",
otherRequiredError: "Response required: enter another value.",
uploadingFile: "Your file is uploading. Please wait several seconds and try again.",
loadingFile: "Loading...",
chooseFile: "Choose file(s)...",
noFileChosen: "No file selected",
filePlaceholder: "Drag and drop a file here or click the button below to select a file to upload.",
confirmDelete: "Are you sure you want to delete this record?",
keyDuplicationError: "This value should be unique.",
addColumn: "Add Column",
addRow: "Add Row",
removeRow: "Remove",
noRowsText: "There are no rows.",
addPanel: "Add new",
removePanel: "Remove",
showDetails: "Show Details",
hideDetails: "Hide Details",
choices_Item: "item",
choices_Choice: "Choice option",
matrix_column: "Column",
matrix_row: "Row",
multipletext_itemname: "text",
savingData: "The results are being saved on the server...",
savingDataError: "An error occurred and we could not save the results.",
savingDataSuccess: "The results were saved successfully!",
savingExceedSize: "Your response exceeds 64KB. Please reduce the size of your file(s) and try again or contact the survey owner.",
saveAgainButton: "Try again",
timerMin: "min",
timerSec: "sec",
timerSpentAll: "You have spent {0} on this page and {1} in total.",
timerSpentPage: "You have spent {0} on this page.",
timerSpentSurvey: "You have spent {0} in total.",
timerLimitAll: "You have spent {0} of {1} on this page and {2} of {3} in total.",
timerLimitPage: "You have spent {0} of {1} on this page.",
timerLimitSurvey: "You have spent {0} of {1} in total.",
clearCaption: "Clear",
signaturePlaceHolder: "Sign here",
signaturePlaceHolderReadOnly: "No signature",
chooseFileCaption: "Select File",
takePhotoCaption: "Take Photo",
photoPlaceholder: "Click the button below to take a photo using the camera.",
fileOrPhotoPlaceholder: "Drag and drop or select a file to upload or take a photo using the camera.",
replaceFileCaption: "Replace file",
removeFileCaption: "Remove this file",
booleanCheckedLabel: "Yes",
booleanUncheckedLabel: "No",
confirmRemoveFile: "Are you sure that you want to remove this file: {0}?",
confirmRemoveAllFiles: "Are you sure that you want to remove all files?",
questionTitlePatternText: "Question Title",
modalCancelButtonText: "Cancel",
modalApplyButtonText: "Apply",
filterStringPlaceholder: "Type to search...",
emptyMessage: "No data to display",
loadingPage: "Loading...",
loadingData: "Loading...",
noEntriesText: "No entries yet.\nClick the button below to add a new entry.",
noEntriesReadonlyText: "No entries",
tabTitlePlaceholder: "New Panel",
more: "More",
tagboxDoneButtonCaption: "OK",
selectToRankEmptyRankedAreaText: "All choices are selected for ranking",
selectToRankEmptyUnrankedAreaText: "Drag choices here to rank them",
ok: "OK",
cancel: "Cancel",
createCustomItem: "Create \"{0}\" item...",
};
// Uncomment the lines below if you create a custom dictionary.
// Replace "en" with a custom locale code (for example, "fr" or "de"),
// Replace `englishStrings` with the name of the variable that contains the custom dictionary.
//setupLocale({ localeCode: "en", strings: englishStrings, nativeName: "English", englishName: "English" });
var surveyLocalization = {
currentLocaleValue: "",
defaultLocaleValue: "en",
locales: {},
localeNames: {},
localeNamesInEnglish: {},
localeDirections: {},
supportedLocales: [],
useEnglishNames: false,
//TODO remove on next week
get showNamesInEnglish() { return this.useEnglishNames; },
set showNamesInEnglish(val) { this.useEnglishNames = val; },
setupLocale(localeConfig) {
const loc = localeConfig.localeCode;
this.locales[loc] = localeConfig.strings;
this.localeNames[loc] = localeConfig.nativeName;
this.localeNamesInEnglish[loc] = localeConfig.englishName;
if (localeConfig.rtl !== undefined) {
this.localeDirections[loc] = localeConfig.rtl ? "rtl" : "ltr";
}
},
get currentLocale() {
return this.currentLocaleValue === this.defaultLocaleValue ? "" : this.currentLocaleValue;
},
set currentLocale(val) {
if (val === "cz")
val = "cs";
this.currentLocaleValue = val;
},
get defaultLocale() {
return this.defaultLocaleValue;
},
set defaultLocale(val) {
if (val === "cz")
val = "cs";
this.defaultLocaleValue = val;
},
getLocaleStrings(loc) {
return this.locales[loc];
},
getString: function (strName, locale = null) {
const locs = new Array();
const addLocaleCore = (locName) => {
const strs = this.locales[locName];
if (!!strs)
locs.push(strs);
};
const addLocale = (locName) => {
if (!locName)
return;
addLocaleCore(locName);
const index = locName.indexOf("-");
if (index < 1)
return;
locName = locName.substring(0, index);
addLocaleCore(locName);
};
addLocale(locale);
addLocale(this.currentLocale);
addLocale(this.defaultLocale);
if (this.defaultLocale !== "en") {
addLocaleCore("en");
}
for (let i = 0; i < locs.length; i++) {
const res = locs[i][strName];
if (res !== undefined)
return res;
}
return this.onGetExternalString(strName, locale);
},
getLocaleName(loc, inEnglish) {
if (!loc)
return "";
if (inEnglish === undefined)
inEnglish = this.showNamesInEnglish;
const firstNames = inEnglish ? this.localeNamesInEnglish : this.localeNames;
const secondNames = inEnglish ? this.localeNames : this.localeNamesInEnglish;
return firstNames[loc] || secondNames[loc] || loc;
},
getLocales: function (removeDefaultLoc = false) {
var res = [];
res.push("");
var locs = this.locales;
if (this.supportedLocales && this.supportedLocales.length > 0) {
locs = {};
for (var i = 0; i < this.supportedLocales.length; i++) {
locs[this.supportedLocales[i]] = true;
}
}
for (var key in locs) {
if (removeDefaultLoc && key == this.defaultLocale)
continue;
res.push(key);
}
var locName = (loc) => {
return this.getLocaleName(loc).toLowerCase();
};
res.sort((a, b) => {
var str1 = locName(a);
var str2 = locName(b);
if (str1 === str2)
return 0;
return str1 < str2 ? -1 : 1;
});
return res;
},
onGetExternalString: function (name, locale) { return undefined; }
};
function getLocaleString(strName, locale = null) {
return surveyLocalization.getString(strName, locale);
}
function getLocaleStrings(locale) {
return surveyLocalization.getLocaleStrings(locale);
}
function setupLocale(localeConfig) {
surveyLocalization.setupLocale(localeConfig);
}
var surveyStrings = englishStrings;
surveyLocalization.locales["en"] = englishStrings;
surveyLocalization.localeNames["en"] = "english";
function ensureLocString(target, options, key) {
let locString = target.getLocalizableString(key);
if (!locString) {
let defaultStr;
if (typeof options.localizable === "object" && options.localizable.defaultStr) {
defaultStr = options.localizable.defaultStr;
}
locString = target.createLocalizableString(key, target, true, defaultStr);
if (typeof options.localizable === "object" &&
typeof options.localizable.onGetTextCallback === "function") {
locString.onGetTextCallback = options.localizable.onGetTextCallback;
}
}
}
function getLocStringValue(target, options, key) {
ensureLocString(target, options, key);
let res = target.getLocalizableStringText(key);
if (!!res)
return res;
if (typeof options.localizable === "object" && options.localizable.defaultStr) {
const loc = !!target.getLocale ? target.getLocale() : "";
return getLocaleString(options.localizable.defaultStr, loc);
}
return "";
}
function property(options = {}) {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
return function (target, key) {
let processComputedUpdater = (obj, val) => {
if (!!val && typeof val === "object" && val.type === ComputedUpdater.ComputedUpdaterType) {
Base.startCollectDependencies(() => obj[key] = val.updater(), obj, key);
const result = val.updater();
const dependencies = Base.finishCollectDependencies();
val.setDependencies(dependencies);
if (obj.dependencies[key]) {
obj.dependencies[key].dispose();
}
obj.dependencies[key] = val;
return result;
}
return val;
};
if (!options || !options.localizable) {
Object.defineProperty(target, key, {
get: function () {
// const serializationProperty = Serializer.getProperty(target.getType(), key);
// if(!!serializationProperty && options.defaultValue !== undefined) {
// ConsoleWarnings.error("remove defaultValue from @property for class " + target.getType() + " property name is " + key);
// }
let defaultVal = null;
if (!!options) {
if (typeof options.getDefaultValue === "function") {
defaultVal = options.getDefaultValue(this);
}
if (options.defaultValue !== undefined) {
defaultVal = options.defaultValue;
}
}
return this.getPropertyValue(key, defaultVal);
},
set: function (val) {
const newValue = processComputedUpdater(this, val);
const prevValue = this.getPropertyValue(key);
if (newValue !== prevValue) {
this.setPropertyValue(key, newValue);
if (!!options && options.onSet) {
options.onSet(newValue, this, prevValue);
}
}
},
});
}
else {
Object.defineProperty(target, key, {
get: function () {
return getLocStringValue(this, options, key);
},
set: function (val) {
ensureLocString(this, options, key);
const newValue = processComputedUpdater(this, val);
this.setLocalizableStringText(key, newValue);
if (!!options && options.onSet) {
options.onSet(newValue, this);
}
},
});
Object.defineProperty(target, typeof options.localizable === "object" && !!options.localizable.name ?
options.localizable.name : "loc" + key.charAt(0).toUpperCase() + key.slice(1), {
get: function () {
ensureLocString(this, options, key);
return this.getLocalizableString(key);
},
});
}
};
}
function ensureArray(target, options, key) {
target.ensureArray(key, (item, index) => {
const handler = !!options ? options.onPush : null;
handler && handler(item, index, target);
}, (item, index) => {
const handler = !!options ? options.onRemove : null;
handler && handler(item, index, target);
});
}
function propertyArray(options) {
return function (target, key) {
Object.defineProperty(target, key, {
get: function () {
ensureArray(this, options, key);
return this.getPropertyValue(key);
},
set: function (val) {
ensureArray(this, options, key);
const arr = this.getPropertyValue(key);
if (val === arr) {
return;
}
if (arr) {
arr.splice(0, arr.length, ...(val || []));
}
else {
this.setPropertyValue(key, val);
}
if (!!options && options.onSet) {
options.onSet(val, this);
}
},
});
};
}
/**
* Contains information about a property of a survey element (page, panel, questions, and etc).
* @see addProperty
* @see removeProperty
* @see [Add Properties](https://surveyjs.io/Documentation/Survey-Creator#addproperties)
* @see [Remove Properties](https://surveyjs.io/Documentation/Survey-Creator#removeproperties)
*/
class JsonObjectProperty {
constructor(classInfo, name, isRequired = false) {
this.name = name;
this.isRequiredValue = false;
this.isUniqueValue = false;
this.isSerializable = true;
this.isLightSerializable = true;
this.isCustom = false;
this.isDynamicChoices = false; //TODO obsolete, use dependsOn attribute
this.isBindable = false;
this.category = "";
this.categoryIndex = -1;
this.visibleIndex = -1;
this.maxLength = -1;
this.isArray = false;
this.classInfoValue = classInfo;
this.isRequiredValue = isRequired;
this.idValue = JsonObjectProperty.Index++;
}
get id() {
return this.idValue;
}
get classInfo() {
return this.classInfoValue;
}
get type() {
return this.typeValue ? this.typeValue : "string";
}
set type(value) {
if (value === "itemvalues")
value = "itemvalue[]";
if (value === "textitems")
value = "textitem[]";
this.typeValue = value;
if (this.typeValue.indexOf("[]") === this.typeValue.length - 2) {
this.isArray = true;
this.className = this.typeValue.substring(0, this.typeValue.length - 2);
}
}
get locationInTable() {
const res = this.locationInTableValue;
return res || "column";
}
set locationInTable(val) {
this.locationInTableValue = val;
}
get showMode() {
const res = this.locationInTableValue;
return res === "detail" ? "form" : (res === "column" ? "list" : "");
}
set showMode(val) {
this.locationInTable = val === "form" ? "detail" : (val === "list" ? "column" : undefined);
}
get isRequired() {
return this.isRequiredValue;
}
set isRequired(val) {
if (val !== this.isRequired) {
this.isRequiredValue = val;
if (!!this.classInfo) {
this.classInfo.resetAllProperties();
}
}
}
get isUnique() {
return this.isUniqueValue;
}
set isUnique(val) {
this.isUniqueValue = val;
}
get uniquePropertyName() {
return this.uniquePropertyValue;
}
set uniquePropertyName(val) {
this.uniquePropertyValue = val;
}
isPropertySerializable(obj) {
if (this.isSerializableFunc)
return this.isSerializableFunc(obj);
return this.isSerializable;
}
getDefaultValue(obj) {
let result = !!this.defaultValueFunc ? this.defaultValueFunc(obj) : this.defaultValueValue;
if (!!JsonObjectProperty.getItemValuesDefaultValue &&
Serializer.isDescendantOf(this.className, "itemvalue")) {
result = JsonObjectProperty.getItemValuesDefaultValue(this.defaultValueValue || [], this.className);
}
return result;
}
get defaultValue() {
return this.getDefaultValue(undefined);
}
set defaultValue(newValue) {
this.defaultValueValue = newValue;
}
isDefaultValue(value) {
return this.isDefaultValueByObj(undefined, value);
}
isDefaultValueByObj(obj, value) {
if (this.isLocalizable)
return value === null || value === undefined;
const dValue = this.getDefaultValue(obj);
if (dValue !== undefined) {
if (typeof dValue !== "object")
return dValue === value;
return Helpers.isTwoValueEquals(value, dValue, false, true, false);
}
return ((value === false && (this.type == "boolean" || this.type == "switch") && !this.defaultValueFunc) ||
value === "" || Helpers.isValueEmpty(value));
}
getSerializableValue(obj, storeDefaults) {
if (!!this.onSerializeValue)
return this.onSerializeValue(obj);
const value = this.getValue(obj);
if (value === undefined || value === null)
return undefined;
if (!storeDefaults && this.isDefaultValueByObj(obj, value))
return undefined;
return value;
}
getValue(obj) {
if (this.onGetValue) {
obj = this.getOriginalObj(obj);
return this.onGetValue(obj);
}
if (this.serializationProperty && !!obj[this.serializationProperty])
return obj[this.serializationProperty].getJson();
return obj[this.name];
}
getPropertyValue(obj) {
if (this.isLocalizable) {
return !!obj[this.serializationProperty]
? obj[this.serializationProperty].text
: null;
}
return this.getValue(obj);
}
get hasToUseSetValue() {
return this.onSetValue || this.serializationProperty;
}
settingValue(obj, value) {
if (!this.onSettingValue || obj.isLoadingFromJson)
return value;
return this.onSettingValue(obj, value);
}
setValue(obj, value, jsonConv) {
if (this.onSetValue) {
obj = this.getOriginalObj(obj);
this.onSetValue(obj, value, jsonConv);
}
else {
if (this.serializationProperty && !!obj[this.serializationProperty])
obj[this.serializationProperty].setJson(value, true);
else {
if (value && typeof value === "string") {
if (this.type == "number") {
value = parseInt(value);
}
if (this.type == "boolean" || this.type == "switch") {
value = value.toLowerCase() === "true";
}
}
obj[this.name] = value;
}
}
}
validateValue(value) {
const choices = this.choices;
if (!Array.isArray(choices) || choices.length === 0)
return true;
return choices.indexOf(value) > -1;
}
getObjType(objType) {
if (!this.classNamePart)
return objType;
return objType.replace(this.classNamePart, "");
}
/**
* Depricated, please use getChoices
*/
get choices() {
return this.getChoices(null);
}
get hasChoices() {
return !!this.choicesValue || !!this.choicesfunc;
}
getChoices(obj, choicesCallback = null) {
if (this.choicesValue != null)
return this.choicesValue;
if (this.choicesfunc != null)
return this.choicesfunc(obj, choicesCallback);
return null;
}
setChoices(value, valueFunc = null) {
this.choicesValue = value;
this.choicesfunc = valueFunc;
}
getBaseValue() {
if (!this.baseValue)
return "";
if (typeof this.baseValue == "function")
return this.baseValue();
return this.baseValue;
}
setBaseValue(val) {
this.baseValue = val;
}
get readOnly() {
return this.readOnlyValue != null ? this.readOnlyValue : false;
}
set readOnly(val) {
this.readOnlyValue = val;
}
isEnable(obj) {
if (this.readOnly)
return false;
if (!obj || !this.enableIf)
return true;
return this.enableIf(this.getOriginalObj(obj));
}
isVisible(layout, obj = null) {
let isLayout = !this.layout || !layout || this.layout === layout;
if (!this.visible || !isLayout)
return false;
if (!!this.visibleIf && !!obj) {
return this.visibleIf(this.getOriginalObj(obj));
}
return true;
}
getOriginalObj(obj) {
if (obj && obj.getOriginalObj) {
const orjObj = obj.getOriginalObj();
if (orjObj && Serializer.findProperty(orjObj.getType(), this.name)) {
return orjObj;
}
}
return obj;
}
get visible() {
return this.visibleValue != null ? this.visibleValue : true;
}
set visible(val) {
this.visibleValue = val;
}
isAvailableInVersion(ver) {
if (!!this.alternativeName || this.oldName)
return true;
return this.isAvailableInVersionCore(ver);
}
getSerializedName(ver) {
if (!this.alternativeName)
return this.name;
return this.isAvailableInVersionCore(ver) ? this.name : this.alternativeName || this.oldName;
}
getSerializedProperty(obj, ver) {
if (!this.oldName || this.isAvailableInVersionCore(ver))
return this;
if (!obj || !obj.getType)
return null;
return Serializer.findProperty(obj.getType(), this.oldName);
}
isAvailableInVersionCore(ver) {
if (!ver || !this.version)
return true;
return Helpers.compareVerions(this.version, ver) <= 0;
}
get isLocalizable() {
return this.isLocalizableValue != null ? this.isLocalizableValue : false;
}
set isLocalizable(val) {
this.isLocalizableValue = val;
}
get dataList() {
return Array.isArray(this.dataListValue) ? this.dataListValue : [];
}
set dataList(val) {
this.dataListValue = val;
}
mergeWith(prop) {
var valuesNames = JsonObjectProperty.mergableValues;
for (var i = 0; i < valuesNames.length; i++) {
this.mergeValue(prop, valuesNames[i]);
}
}
addDependedProperty(name) {
if (!this.dependedProperties) {
this.dependedProperties = [];
}
if (this.dependedProperties.indexOf(name) < 0) {
this.dependedProperties.push(name);
}
}
getDependedProperties() {
return !!this.dependedProperties ? this.dependedProperties : [];
}
schemaType() {
if (this.className === "choicesByUrl")
return undefined;
if (this.className === "string")
return this.className;
if (!!this.className)
return "array";
if (!!this.baseClassName)
return "array";
if (this.type == "switch")
return "boolean";
if (this.type == "boolean" || this.type == "number")
return this.type;
return "string";
}
schemaRef() {
if (!!this.className)
return this.className;
return undefined;
}
mergeValue(prop, valueName) {
if (this[valueName] == null && prop[valueName] != null) {
this[valueName] = prop[valueName];
}
}
}
JsonObjectProperty.Index = 1;
JsonObjectProperty.mergableValues = [
"typeValue",
"choicesValue",
"baseValue",
"readOnlyValue",
"visibleValue",
"isSerializable",
"isLightSerializable",
"isCustom",
"isBindable",
"isUnique",
"uniquePropertyName",
"isDynamicChoices",
"isLocalizableValue",
"className",
"alternativeName",
"oldName",
"layout",
"version",
"classNamePart",
"baseClassName",
"defaultValue",
"defaultValueFunc",
"serializationProperty",
"onGetValue",
"onSetValue",
"onSettingValue",
"displayName",
"category",
"categoryIndex",
"visibleIndex",
"nextToProperty",
"overridingProperty",
"locationInTable",
"dependedProperties",
"visibleIf",
"enableIf",
"onExecuteExpression",
"onPropertyEditorUpdate",
"maxLength",
"maxValue",
"minValue",
"dataListValue",
];
class CustomPropertiesCollection {
static addProperty(clas