UNPKG

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
/*! * 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