survey-analytics
Version:
SurveyJS Dashboard is a UI component for visualizing and analyzing survey data. It interprets the form JSON schema to identify question types and renders collected responses using interactive charts and tables.
1 lines • 3.29 MB
Source Map (JSON)
{"version":3,"file":"survey.analytics.tabulator.mjs","sources":["../../src/tables/extensions/tableextensions.ts","../../src/tables/extensions/rowextensions.ts","../../src/tables/extensions/headerextensions.ts","../../src/tables/config.ts","../../src/tables/extensions/columnextensions.ts","../../src/tables/extensions/detailsextensions.ts","../../src/tables/columns.ts","../../src/tables/columnbuilder.ts","../../src/tables/table.ts","../../src/tables/custom_jspdf_font.ts","../../src/tables/tabulator.ts","../../src/entries/tabulator-es.ts"],"sourcesContent":["import { Table } from \"../table\";\n\nexport interface ITableExtension {\n location: string;\n name: string;\n visibleIndex: number;\n render: (table: Table, opt: any) => HTMLElement;\n destroy?: () => void;\n}\n\nexport class TableExtensions {\n constructor(private table: Table) {}\n private static extensions: { [location: string]: Array<ITableExtension> } = {};\n private renderedExtensions: Array<ITableExtension> = [];\n\n public render(targetNode: HTMLElement, location: string, options?: any) {\n var extensions = TableExtensions.extensions[location];\n if(!!extensions) {\n extensions = this.sortExtensions(extensions);\n extensions.forEach((extension) => {\n if(!!extension.render && this.table.allowExtension(extension)) {\n var action = extension.render(this.table, options);\n if(!!action) {\n targetNode.appendChild(action);\n this.renderedExtensions.push(extension);\n }\n }\n });\n }\n }\n\n public destroy() {\n this.renderedExtensions.forEach((extension) => {\n if(!!extension.destroy) extension.destroy();\n });\n this.renderedExtensions = [];\n }\n\n public static registerExtension(extension: ITableExtension) {\n if(!this.extensions[extension.location])\n this.extensions[extension.location] = [];\n this.extensions[extension.location].push(extension);\n }\n\n private static removeExtension(extension: ITableExtension) {\n if(!extension) {\n return;\n }\n const extensions = TableExtensions.extensions[extension.location];\n const index = extensions.indexOf(extension);\n if(index >= 0) {\n extensions.splice(index, 1);\n }\n }\n\n public static unregisterExtension(\n location: string,\n actionName: string\n ) {\n if(!actionName) {\n return;\n }\n if(!!location) {\n const extension = TableExtensions.findExtension(location, actionName);\n TableExtensions.removeExtension(extension);\n } else {\n Object.keys(this.extensions).forEach((location:string) => TableExtensions.unregisterExtension(location, actionName));\n }\n }\n\n public static findExtension(\n location: string,\n actionName: string\n ): ITableExtension {\n if(!this.extensions[location]) return null;\n var extension = this.extensions[location].filter(function (\n extension: ITableExtension\n ) {\n return extension.name == actionName;\n })[0];\n return extension || null;\n }\n\n public static getExtensions(location: string): Array<ITableExtension> {\n if(!this.extensions[location]) return [];\n return this.extensions[location].slice(0);\n }\n\n private sortExtensions(extensions: Array<ITableExtension>) {\n if(!Array.isArray(extensions)) return;\n return []\n .concat(extensions.filter((extension) => extension.visibleIndex >= 0))\n .sort((firstExtension, secondExtension) => {\n return firstExtension.visibleIndex - secondExtension.visibleIndex;\n });\n }\n}\n","import { Table } from \"../table\";\nimport { localization } from \"../../localizationManager\";\nimport { DocumentHelper } from \"../../utils\";\nimport { TableExtensions } from \"./tableextensions\";\n\nTableExtensions.registerExtension({\n location: \"row\",\n name: \"details\",\n visibleIndex: 0,\n render: (_table: Table, options: any) => {\n const btn = DocumentHelper.createSvgButton(\"detail\");\n btn.title = localization.getString(\"showMinorColumns\");\n btn.className += \" sa-table__row-extension\";\n btn.onclick = () => {\n options.row.toggleDetails();\n };\n return btn;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"row\",\n name: \"select\",\n visibleIndex: -1,\n render: function (_table, opt) {\n var row = opt.row;\n var checkbox = <HTMLInputElement>DocumentHelper.createElement(\n \"input\",\n \"sa-table__row-extension\",\n {\n type: \"checkbox\",\n }\n );\n checkbox.checked = row.getIsSelected();\n checkbox.onchange = function () {\n row.toggleSelect();\n };\n return checkbox;\n },\n});\n","import { localization } from \"../../localizationManager\";\nimport { Table } from \"../table\";\nimport { DocumentHelper } from \"../../utils\";\nimport { TableExtensions } from \"./tableextensions\";\n\nTableExtensions.registerExtension({\n location: \"header\",\n name: \"filter\",\n visibleIndex: 0,\n render: function (table: Table): HTMLElement {\n const input = DocumentHelper.createInput(\n \"sa-table__global-filter sa-table__header-extension\",\n localization.getString(\"filterPlaceholder\")\n );\n input.onchange = (event: any) => {\n table.applyFilter(event.target.value);\n };\n return input;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"header\",\n name: \"showcolumn\",\n visibleIndex: 2,\n destroy: function () {\n this.onDestroy();\n },\n render: function (table: Table): HTMLElement {\n const dropdown = DocumentHelper.createElement(\n \"select\",\n \"sa-table__show-column sa-table__header-extension\"\n );\n\n function update() {\n var hiddenColumns = table.columns.filter(\n (column: any) => !column.isVisible\n );\n if(hiddenColumns.length == 0) {\n dropdown.style.display = \"none\";\n return;\n }\n dropdown.style.display = \"inline-block\";\n dropdown.innerHTML = \"\";\n var option = DocumentHelper.createElement(\"option\", \"\", {\n text: localization.getString(\"showColumn\"),\n disabled: true,\n selected: true,\n });\n dropdown.appendChild(option);\n\n hiddenColumns.forEach((column: any) => {\n var text = column.displayName || column.name;\n if(!!text && text.length > 20) {\n text = text.substring(0, 20) + \"...\";\n }\n var option = DocumentHelper.createElement(\"option\", \"\", {\n text: text,\n title: column.displayName,\n value: column.name,\n });\n dropdown.appendChild(option);\n });\n }\n\n dropdown.onchange = (e: any) => {\n const val = e.target.value;\n e.stopPropagation();\n if(!val) return;\n table.setColumnVisibility(val, true);\n };\n\n update();\n\n var onVisibilityChangedCallback = () => {\n update();\n };\n\n table.onColumnsVisibilityChanged.add(onVisibilityChangedCallback);\n\n this.onDestroy = () => {\n table.onColumnsVisibilityChanged.remove(onVisibilityChangedCallback);\n };\n return dropdown;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"header\",\n name: \"showentries\",\n visibleIndex: 3,\n render: function (table: Table): HTMLElement {\n if(table.options.paginationEnabled === false) {\n return DocumentHelper.createElement(\"div\");\n }\n function getEntriesDropdown(table: Table): HTMLElement {\n const el = <HTMLSelectElement>DocumentHelper.createElement(\"select\");\n var optionsValues = table.paginationSizeSelector || [\"1\", \"5\", \"10\", \"25\", \"50\", \"75\", \"100\"];\n optionsValues.forEach(function (val) {\n var option = DocumentHelper.createElement(\"option\", \"\", {\n value: val,\n text: val,\n });\n el.appendChild(option);\n });\n el.value = String(table.getPageSize());\n\n el.onchange = () => {\n table.setPageSize(Number(el.value));\n };\n\n return el;\n }\n const selectorContainer = DocumentHelper.createElement(\n \"div\",\n \"sa-table__entries\"\n );\n const spaceSpan = DocumentHelper.createElement(\"span\", \"sa-table__header-space\");\n const showSpan = DocumentHelper.createElement(\n \"span\",\n \"sa-table__entries-label sa-table__entries-label--right\",\n {\n innerText: localization.getString(\"showLabel\"),\n }\n );\n const entriesSpan = DocumentHelper.createElement(\n \"span\",\n \"sa-table__entries-label sa-table__entries-label--left\",\n {\n innerText: localization.getString(\"entriesLabel\"),\n }\n );\n\n selectorContainer.appendChild(spaceSpan);\n selectorContainer.appendChild(showSpan);\n selectorContainer.appendChild(getEntriesDropdown(table));\n selectorContainer.appendChild(entriesSpan);\n return selectorContainer;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"header\",\n name: \"removerows\",\n visibleIndex: -1,\n render: function (table) {\n var btn = DocumentHelper.createElement(\n \"button\",\n \"sa-table__btn sa-table__btn--green sa-table__header-extension \",\n { innerText: localization.getString(\"removeRows\") }\n );\n btn.onclick = function () {\n table.getCreatedRows().forEach(function (row) {\n if(row.getIsSelected()) {\n row.remove();\n }\n });\n };\n return btn;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"header\",\n name: \"changelocale\",\n visibleIndex: 1,\n render: function (table) {\n var locales = table.getLocales();\n if(table.options.disableLocaleSwitch || locales.length < 2) return null;\n const el = <HTMLSelectElement>(\n DocumentHelper.createElement(\"select\", \"sa-table__header-extension\", {})\n );\n var optionsValues = [localization.getString(\"changeLocale\")].concat(\n locales\n );\n optionsValues.forEach(function (val) {\n var option = DocumentHelper.createElement(\"option\", \"\", {\n value: val,\n text: localization.localeNames[val] || localization.getString(val) || val,\n });\n el.appendChild(option);\n });\n el.onchange = () => {\n table.locale = el.value;\n };\n return el;\n },\n});\n\nexport var HeaderExtensions;","import { Question, SurveyModel } from \"survey-core\";\nimport { ITableOptions, Table } from \"./table\";\n\nexport enum QuestionLocation {\n Column,\n Row,\n}\nexport enum ColumnDataType {\n Text,\n FileLink,\n Image,\n NestedTable,\n Html\n}\n\nexport interface ICellData {\n question: Question;\n displayValue: any;\n}\n\nexport interface IColumnData {\n name: string;\n displayName: string;\n dataType: ColumnDataType;\n isVisible: boolean;\n isPublic: boolean;\n location: QuestionLocation;\n width?: string | number;\n isComment?: boolean;\n}\nexport interface IColumn extends IColumnData {\n visibleIndex?: number;\n fromJSON(json: any): void;\n getCellData(table: Table, data: any): ICellData;\n}\n\nexport interface ITableState {\n locale?: string;\n elements?: IColumnData[];\n pageSize?: number;\n}\n\nexport interface IPermission {\n name: string;\n isPublic: boolean;\n}\n","import { Table } from \"../table\";\nimport { DocumentHelper } from \"../../utils\";\nimport { localization } from \"../../localizationManager\";\nimport { TableExtensions } from \"./tableextensions\";\nimport { QuestionLocation, IColumn } from \"../config\";\n\nTableExtensions.registerExtension({\n location: \"column\",\n name: \"drag\",\n visibleIndex: 0,\n render: function (table: Table, options: any) {\n const btn = DocumentHelper.createElement(\n \"button\",\n \"sa-table__svg-button sa-table__drag-button\"\n );\n btn.appendChild(DocumentHelper.createSvgElement(\"drag\"));\n btn.addEventListener(\"mousedown\", () => {\n table.enableColumnReorder();\n document.body.addEventListener(\"mouseup\", () => {\n table.disableColumnReorder();\n }, { once: true });\n });\n return btn;\n },\n});\nTableExtensions.registerExtension({\n location: \"column\",\n name: \"sort\",\n visibleIndex: 1,\n render: function (table: Table, options: any) {\n const descTitle = localization.getString(\"descOrder\");\n const ascTitle = localization.getString(\"ascOrder\");\n var btn = DocumentHelper.createSvgButton(\"sorting\");\n btn.title = \"\";\n var direction = \"asc\";\n btn.onclick = () => {\n if(direction == \"asc\") {\n btn.title = descTitle;\n direction = \"desc\";\n } else {\n btn.title = ascTitle;\n direction = \"asc\";\n }\n table.sortByColumn(options.columnName, direction);\n };\n btn.ondrag = (e) => {\n e.stopPropagation();\n };\n return btn;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"column\",\n name: \"hide\",\n visibleIndex: 2,\n render: function (table: Table, options: any) {\n var btn = DocumentHelper.createSvgButton(\"hide\");\n btn.title = localization.getString(\"hideColumn\");\n btn.onclick = () => {\n table.setColumnVisibility(options.columnName, false);\n };\n return btn;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"column\",\n name: \"movetodetails\",\n visibleIndex: 3,\n render: function (table: Table, options: any) {\n const button = DocumentHelper.createSvgButton(\"movetodetails\");\n button.title = localization.getString(\"moveToDetail\");\n button.onclick = (e) => {\n e.stopPropagation();\n table.setColumnLocation(options.columnName, QuestionLocation.Row);\n };\n return button;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"column\",\n name: \"filter\",\n visibleIndex: 4,\n render: function (table: Table, options: any) {\n var el = DocumentHelper.createInput(\n \"sa-table__filter\",\n localization.getString(\"filterPlaceholder\")\n );\n el.onclick = (e) => e.stopPropagation();\n el.onchange = (e) => {\n table.applyColumnFilter(options.columnName, el.value);\n };\n return el;\n },\n});\n\nTableExtensions.registerExtension({\n location: \"column\",\n name: \"makepublic\",\n visibleIndex: -1,\n render: function (table: Table, options: any) {\n const button = DocumentHelper.createElement(\"button\");\n const makePrivateSvg = DocumentHelper.createSvgElement(\"makeprivate\");\n const makePublicSvg = DocumentHelper.createSvgElement(\"makepublic\");\n const column = table.getColumnByName(options.columnName);\n\n updateState(column);\n button.appendChild(makePrivateSvg);\n button.appendChild(makePublicSvg);\n button.onclick = (e) => {\n e.stopPropagation();\n column.isPublic = !column.isPublic;\n updateState(column);\n table.onPermissionsChangedCallback &&\n table.onPermissionsChangedCallback(table);\n };\n\n function updateState(column: IColumn) {\n if(column.isPublic) {\n button.className = \"sa-table__svg-button\";\n button.title = localization.getString(\"makePrivateColumn\");\n makePrivateSvg.style.display = \"none\";\n makePublicSvg.style.display = \"block\";\n } else {\n button.className = \"sa-table__svg-button sa-table__svg-button--active\";\n button.title = localization.getString(\"makePublicColumn\");\n makePrivateSvg.style.display = \"block\";\n makePublicSvg.style.display = \"none\";\n }\n }\n return button;\n },\n});\n","import { TableRow } from \"../table\";\nimport { Table } from \"../table\";\nimport { DocumentHelper } from \"../../utils\";\nimport { ColumnDataType, QuestionLocation } from \"../config\";\nimport { TableExtensions } from \"./tableextensions\";\nimport { localization } from \"../../localizationManager\";\n\nexport class Details {\n constructor(\n protected table: Table,\n private row: TableRow,\n protected targetNode: HTMLElement\n ) {\n this.detailsTable = DocumentHelper.createElement(\n \"table\",\n \"sa-table__detail-table\"\n );\n this.table.onColumnsLocationChanged.add(() => {\n this.close();\n });\n }\n private detailsTable: HTMLElement;\n protected location = \"details\";\n\n public open(): void {\n this.detailsTable.innerHTML = \"\";\n var rows: HTMLElement[] = [];\n this.table.columns\n .filter((column) => column.location === QuestionLocation.Row && column)\n .forEach((column) => {\n var row = DocumentHelper.createElement(\"tr\", \"sa-table__detail\");\n var td1 = DocumentHelper.createElement(\"td\");\n td1.appendChild(document.createTextNode(column.displayName));\n var td2 = DocumentHelper.createElement(\"td\");\n td2.textContent = this.row.getRowData()[column.name];\n if(column.dataType === ColumnDataType.Image) {\n td2.innerHTML = \"<image src='\" + td2.textContent + \"'/>\";\n }\n var td3 = DocumentHelper.createElement(\"td\");\n td3.appendChild(this.createShowAsColumnButton(column.name));\n row.appendChild(td1);\n row.appendChild(td2);\n row.appendChild(td3);\n rows.push(row);\n });\n var row = DocumentHelper.createElement(\"tr\", \"sa-table__detail\");\n var td = DocumentHelper.createElement(\"td\", \"\", { colSpan: 3 });\n var extensions = new TableExtensions(this.table);\n extensions.render(td, \"details\", { row: this.row });\n if(td.children.length != 0) {\n row.appendChild(td);\n rows.push(row);\n }\n\n rows.forEach((row) => {\n this.detailsTable.appendChild(row);\n });\n this.targetNode.appendChild(this.detailsTable);\n }\n\n protected createShowAsColumnButton = (columnName: string): HTMLElement => {\n const button = DocumentHelper.createElement(\n \"button\",\n \"sa-table__btn sa-table__btn--gray\"\n );\n button.appendChild(document.createTextNode(localization.getString(\"showAsColumn\")));\n button.onclick = (e) => {\n e.stopPropagation();\n this.table.setColumnLocation(columnName, QuestionLocation.Column);\n };\n\n return button;\n };\n\n public close() {\n if(!!this.detailsTable.parentNode) {\n this.detailsTable.parentNode.removeChild(this.detailsTable);\n }\n }\n}\n","import { ItemValue, MatrixRowModel, Question, QuestionCheckboxModel, QuestionCompositeModel, QuestionCustomModel, QuestionDropdownModel, QuestionFileModel, QuestionMatrixDropdownModel, QuestionMatrixDynamicModel, QuestionMatrixModel, QuestionPanelDynamicModel, QuestionRadiogroupModel, QuestionSelectBase, QuestionTagboxModel, settings } from \"survey-core\";\nimport { createImagesContainer, createLinksContainer } from \"../utils\";\nimport { ICellData, IColumn, ColumnDataType, QuestionLocation, IColumnData } from \"./config\";\nimport { ITableOptions, Table } from \"./table\";\n\nexport class BaseColumn<T extends Question = Question> implements IColumn {\n dataType: ColumnDataType;\n isVisible: boolean = true;\n isPublic: boolean = true;\n location: QuestionLocation = QuestionLocation.Column;\n width?: string | number;\n visibleIndex?: number;\n isComment?: boolean;\n private nameValue: string;\n private displayNameValue?: string;\n\n constructor(protected question: T, protected table: Table) {\n this.dataType = this.getDataType();\n this.updateWhenQuestionIsReady(question, table);\n }\n\n protected updateWhenQuestionIsReady(question: Question, table: Table) {\n if(!question || question.isReady) return;\n question.waitForQuestionIsReady().then(() => {\n if(!table.isInitialized) return;\n try {\n table.lockStateChanged();\n table.refresh(!table.isInitTableDataProcessing);\n } finally {\n table.unlockStateChanged();\n }\n });\n }\n\n get name(): string {\n if(!this.nameValue) {\n this.name = this.getName();\n }\n return this.nameValue;\n }\n set name(val: string) {\n this.nameValue = val;\n }\n get displayName(): string {\n if(!this.displayNameValue) {\n this.displayName = this.getDisplayName();\n }\n return this.displayNameValue;\n }\n public set displayName(val: string) {\n this.displayNameValue = val;\n }\n\n protected getDisplayName(): string {\n return this.table.useNamesAsTitles\n ? this.question.name\n : (this.question.locTitle?.renderedHtml || this.question.title || \"\").trim() || this.question.name;\n }\n protected getName(): string {\n return this.question.name;\n }\n protected getDataType(): ColumnDataType {\n return ColumnDataType.Text;\n }\n protected getDisplayValueCore(data: any) {\n return data[this.name];\n }\n\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): any {\n let displayValue = this.getDisplayValueCore(data);\n const question = this.question;\n\n if(!!question) {\n if(options.useValuesAsLabels) {\n displayValue = question.value;\n } else {\n displayValue = question.displayValue;\n }\n }\n return displayValue;\n }\n private formatDisplayValue(displayValue: any) {\n return typeof displayValue === \"string\"\n ? displayValue\n : JSON.stringify(displayValue) || \"\";\n }\n\n public getCellData(table: Table, data: any): ICellData {\n const displayValue = this.getDisplayValue(data, table, table.options);\n return { question: this.question, displayValue: this.formatDisplayValue(displayValue) };\n }\n public toJSON(): IColumnData {\n return {\n name: this.name,\n displayName: this.displayName,\n dataType: this.dataType,\n isVisible: this.isVisible,\n isPublic: this.isPublic,\n location: this.location\n };\n }\n public fromJSON(data: IColumnData) {\n Object.keys(data).forEach(key => {\n this[key] = data[key];\n });\n }\n}\n\nexport class DefaultColumn extends BaseColumn {\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): any {\n return this.getDisplayValueCore(data);\n }\n}\n\nexport class SelectBaseColumn<T extends QuestionSelectBase> extends BaseColumn<T> {\n protected get isOtherInSeparateColumn() {\n return this.question.hasOther && this.question.getStoreOthersAsComment();\n }\n protected getItemDisplayText(item: ItemValue, options: ITableOptions): string {\n return options.useValuesAsLabels ? item.value : item.textOrHtml;\n }\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): string {\n const prevSeparator = settings.choicesSeparator;\n settings.choicesSeparator = table.itemsDelimiter;\n const value = options.useValuesAsLabels ? this.question.renderedValue : this.question.displayValue;\n settings.choicesSeparator = prevSeparator;\n return value;\n }\n}\n\nexport class CheckboxColumn extends SelectBaseColumn<QuestionCheckboxModel | QuestionTagboxModel> {\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): string {\n if(this.isOtherInSeparateColumn) {\n const selectedItems = this.question.selectedItems;\n const res: Array<string> = selectedItems.map(item => this.getItemDisplayText(item, options));\n return res.join(table.itemsDelimiter);\n }\n return super.getDisplayValue(data, table, options);\n }\n}\n\nexport class FlattenedCheckboxColumn extends BaseColumn<QuestionCheckboxModel | QuestionTagboxModel> {\n constructor(question: QuestionCheckboxModel | QuestionTagboxModel, private choiceValue: any, private choiceText: string, table: Table) {\n super(question, table);\n }\n\n protected getDataType(): ColumnDataType {\n return ColumnDataType.Html;\n }\n\n protected getName(): string {\n return `${this.question.name}.${this.choiceValue}`;\n }\n\n protected getDisplayName(): string {\n const questionDisplayName = this.table.useNamesAsTitles\n ? this.question.name\n : (this.question.locTitle?.renderedHtml || this.question.title || \"\").trim() || this.question.name;\n const choiceDisplayText = this.table.useNamesAsTitles ? this.choiceValue : this.choiceText;\n return `${questionDisplayName} - ${choiceDisplayText}`;\n }\n\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): any {\n const questionValue = data[this.question.name];\n if(!Array.isArray(questionValue)) {\n return \"\";\n }\n const index = questionValue.indexOf(this.choiceValue);\n if(index < 0) {\n return \"\";\n }\n // Default to checkmark if not specified\n const displayMode = options.multiSelectColumnValueFormat || \"checkmark\";\n return displayMode === \"checkmark\" ? \"✔\" : (index + 1).toString();\n }\n}\n\nexport class SingleChoiceColumn extends SelectBaseColumn<QuestionDropdownModel | QuestionRadiogroupModel> {\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): string {\n if(this.isOtherInSeparateColumn) {\n const selectedItem = this.question.selectedItem;\n if(!selectedItem) return \"\";\n return this.getItemDisplayText(selectedItem, options);\n }\n return super.getDisplayValue(data, table, options);\n }\n}\n\nexport class CommentColumn<T extends Question = Question> extends BaseColumn<T> {\n protected getName(): string {\n return `${this.question.name}${settings.commentPrefix}`;\n }\n protected getDisplayName(): string {\n return this.question.commentText;\n }\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): string {\n return this.question.comment;\n }\n}\nexport class OtherColumn extends CommentColumn<QuestionSelectBase> {\n protected getDisplayName(): string {\n return this.question.otherText;\n }\n}\n\nexport class MatrixColumn extends BaseColumn<QuestionMatrixModel> {\n private valueName: string;\n private valuePath: string;\n constructor(question: QuestionMatrixModel, private row: MatrixRowModel, table: Table) {\n super(question, table);\n this.valueName = this.question.name;\n this.valuePath = this.row?.value;\n }\n protected getName(): string {\n return this.question.name + \".\" + this.row?.value;\n }\n protected getDisplayName(): string {\n const table = this.table;\n const question = this.question;\n const row = this.row;\n return (table.useNamesAsTitles\n ? question.name\n : (question.title || \"\").trim() || question.name) + \" - \" + (table.useNamesAsTitles ? row?.value : row?.locText.textOrHtml);\n }\n\n protected getDisplayValue(data: any, table: Table, options: ITableOptions) {\n let displayValue = data[this.valueName];\n if(this.valuePath && typeof displayValue === \"object\") {\n displayValue = displayValue[this.valuePath];\n if(displayValue !== undefined) {\n if(Array.isArray(displayValue)) {\n const res = [];\n displayValue.forEach(itemValue => {\n const item = ItemValue.getItemByValue(this.question.columns, itemValue);\n if(!!item) {\n res.push(options.useValuesAsLabels ? item.value : item.locText.textOrHtml);\n }\n }\n );\n displayValue = res.join(table.itemsDelimiter);\n } else {\n const choiceValue = ItemValue.getItemByValue(this.question.columns, displayValue);\n if(!!choiceValue) {\n displayValue = options.useValuesAsLabels ? choiceValue.value : choiceValue.locText.textOrHtml;\n }\n }\n }\n }\n return displayValue;\n }\n}\n\nexport class ImageColumn extends BaseColumn {\n protected getDataType(): ColumnDataType {\n return ColumnDataType.Image;\n }\n}\n\nexport class FileColumn extends BaseColumn<QuestionFileModel> {\n protected getDataType(): ColumnDataType {\n return ColumnDataType.FileLink;\n }\n protected getDisplayValue(data: any, table: Table, options: ITableOptions) {\n let displayValue = this.getDisplayValueCore(data);\n if(Array.isArray(displayValue)) {\n displayValue = Table.showFilesAsImages ? createImagesContainer(\n displayValue\n ).outerHTML : createLinksContainer(\n displayValue\n ).outerHTML;\n }\n return displayValue;\n }\n}\nexport class MatrixDropdownColumn extends BaseColumn<QuestionMatrixDropdownModel> {\n private rowValue: string;\n private colName: string;\n constructor(question: QuestionMatrixDropdownModel, protected row, protected col, table: Table) {\n super(question, table);\n this.rowValue = this.row.value;\n this.colName = this.col.name;\n }\n protected getName(): string {\n return this.question.name + \".\" + this.row.value + \".\" + this.col.name;\n }\n protected getDisplayName(): string {\n const table = this.table;\n const question = this.question;\n return (this.table.useNamesAsTitles\n ? question.name\n : (question.title || \"\").trim() || question.name) + \" - \" + (table.useNamesAsTitles ? this.row.value : this.row.locText.textOrHtml) + \" - \" + (table.useNamesAsTitles ? this.col.name : this.col.locTitle.textOrHtml);\n\n }\n protected getDisplayValue(data: any, table: Table, options: ITableOptions) {\n let displayValue = data[this.question.name];\n const question = this.question;\n if(this.rowValue && this.colName && typeof displayValue === \"object\") {\n let [rowId, colId] = [this.rowValue, this.colName];\n displayValue = question.value;\n if(!options.useValuesAsLabels) {\n displayValue = question.displayValue;\n rowId = question.rows.filter(row => row.value === this.rowValue)[0].text;\n colId = question.getColumnByName(this.colName).title;\n }\n displayValue = (displayValue[rowId] && displayValue[rowId][colId]) || \"\";\n }\n return displayValue;\n }\n}\n\nexport class CustomQuestionColumn extends BaseColumn<QuestionCustomModel> {\n constructor(question: QuestionCustomModel, table: Table) {\n super(question, table);\n this.updateWhenQuestionIsReady(question.contentQuestion, table);\n }\n}\n\nexport class CompositeQuestionColumn extends BaseColumn<QuestionCompositeModel> {\n constructor(question: QuestionCompositeModel, table: Table) {\n super(question, table);\n const questionList: Question[] = [];\n this.question.contentPanel.addQuestionsToList(questionList);\n questionList.forEach((q: Question) => {\n this.updateWhenQuestionIsReady(q, table);\n });\n }\n}\n\nexport class MatrixDynamicColumn extends BaseColumn<QuestionMatrixDynamicModel> {\n protected getDataType(): ColumnDataType {\n return this.table.options.useNestedTables ? ColumnDataType.NestedTable : ColumnDataType.Text;\n }\n\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): any {\n if(table.options.useNestedTables) {\n return this.getDisplayValueCore(data);\n }\n return super.getDisplayValue(data, table, options);\n }\n\n public getCellData(table: Table, data: any): ICellData {\n const displayValue = this.getDisplayValue(data, table, table.options);\n const formattedValue = table.options.useNestedTables && Array.isArray(displayValue)\n ? displayValue\n : (typeof displayValue === \"string\" ? displayValue : JSON.stringify(displayValue) || \"\");\n return { question: this.question, displayValue: formattedValue };\n }\n}\n\nexport class PanelDynamicColumn extends BaseColumn<QuestionPanelDynamicModel> {\n protected getDataType(): ColumnDataType {\n return this.table.options.useNestedTables ? ColumnDataType.NestedTable : ColumnDataType.Text;\n }\n\n protected getDisplayValue(data: any, table: Table, options: ITableOptions): any {\n if(table.options.useNestedTables) {\n return this.getDisplayValueCore(data);\n }\n return super.getDisplayValue(data, table, options);\n }\n\n public getCellData(table: Table, data: any): ICellData {\n const displayValue = this.getDisplayValue(data, table, table.options);\n const formattedValue = table.options.useNestedTables && Array.isArray(displayValue)\n ? displayValue\n : (typeof displayValue === \"string\" ? displayValue : JSON.stringify(displayValue) || \"\");\n return { question: this.question, displayValue: formattedValue };\n }\n}","import { Question, QuestionCheckboxModel, QuestionCompositeModel, QuestionCustomModel, QuestionDropdownModel, QuestionFileModel, QuestionMatrixDropdownModel, QuestionMatrixDynamicModel, QuestionMatrixModel, QuestionPanelDynamicModel, QuestionRadiogroupModel, QuestionSelectBase } from \"survey-core\";\nimport { BaseColumn, CheckboxColumn, CommentColumn, CompositeQuestionColumn, CustomQuestionColumn, FileColumn, FlattenedCheckboxColumn, ImageColumn, MatrixColumn, MatrixDropdownColumn, MatrixDynamicColumn, OtherColumn, PanelDynamicColumn, SelectBaseColumn, SingleChoiceColumn } from \"./columns\";\nimport { IColumn, QuestionLocation } from \"./config\";\nimport { Table } from \"./table\";\n\nexport interface IColumnsBuilder {\n buildColumns(question: Question, table: Table): Array<IColumn>;\n}\nexport class DefaultColumnsBuilder<T extends Question = Question> implements IColumnsBuilder {\n protected createColumn(question: T, table: Table) {\n return new BaseColumn(question, table);\n }\n\n protected buildColumnsCore(question: T, table: Table): Array<IColumn> {\n const columns: Array<IColumn> = [];\n columns.push(this.createColumn(question, table));\n return columns;\n }\n\n public buildColumns(question: T, table: Table): Array<IColumn> {\n const columns = this.buildColumnsCore(question, table);\n if(question.hasComment) {\n columns.push(new CommentColumn(question, table));\n }\n return columns;\n }\n}\n\nexport class ColumnsBuilderFactory {\n public static Instance: ColumnsBuilderFactory = new ColumnsBuilderFactory();\n private constructor() {}\n\n private readonly columnsBuilders: {[index: string]: IColumnsBuilder } = {};\n private readonly defaultColumnsBuilder: IColumnsBuilder = new DefaultColumnsBuilder();\n\n registerBuilderColumn(type: string, columnsBuilder: IColumnsBuilder) {\n this.columnsBuilders[type] = columnsBuilder;\n }\n getColumnsBuilder(type: string) {\n return this.columnsBuilders[type] || this.defaultColumnsBuilder;\n }\n}\n\nexport class SelectBaseColumnsBuilder<T extends QuestionSelectBase> extends DefaultColumnsBuilder<T> {\n public buildColumns(question: T, table: Table): Array<IColumn> {\n const columns = super.buildColumns(question, table);\n if(question.hasOther && question.getStoreOthersAsComment()) {\n columns.push(new OtherColumn(question, table));\n }\n return columns;\n }\n}\nexport class CheckboxColumnsBuilder extends SelectBaseColumnsBuilder<QuestionCheckboxModel> {\n protected createColumn(question: QuestionCheckboxModel, table: Table): BaseColumn<QuestionCheckboxModel> {\n return new CheckboxColumn(question, table);\n }\n protected buildColumnsCore(question: QuestionCheckboxModel, table: Table): Array<IColumn> {\n if(table.options.splitMultiSelectIntoColumns) {\n const columns: Array<IColumn> = [];\n question.visibleChoices.forEach(choice => {\n columns.push(new FlattenedCheckboxColumn(question, choice.value, choice.text, table));\n });\n return columns;\n }\n return super.buildColumnsCore(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"checkbox\", new CheckboxColumnsBuilder());\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"tagbox\", new CheckboxColumnsBuilder());\nexport class SingleChoiceColumnsBuilder extends SelectBaseColumnsBuilder<QuestionDropdownModel | QuestionRadiogroupModel> {\n protected createColumn(question: QuestionDropdownModel | QuestionRadiogroupModel, table: Table): BaseColumn<QuestionDropdownModel | QuestionRadiogroupModel> {\n return new SingleChoiceColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"radiogroup\", new SingleChoiceColumnsBuilder());\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"dropdown\", new SingleChoiceColumnsBuilder());\n\nexport class MatrixColumnsBuilder extends DefaultColumnsBuilder<QuestionMatrixModel> {\n protected buildColumnsCore(questionBase: Question, table: Table): IColumn[] {\n const question = <QuestionMatrixModel>questionBase;\n const columns = [];\n question.rows.forEach(row => {\n columns.push(new MatrixColumn(question, row, table));\n });\n return columns;\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"matrix\", new MatrixColumnsBuilder());\n\nexport class ImageColumnsBuilder extends DefaultColumnsBuilder {\n protected createColumn(question: Question, table: Table): ImageColumn {\n return new ImageColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"signaturepad\", new ImageColumnsBuilder());\n\nexport class FileColumnsBuilder extends DefaultColumnsBuilder<QuestionFileModel> {\n protected createColumn(question: QuestionFileModel, table: Table): FileColumn {\n return new FileColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"file\", new FileColumnsBuilder());\n\nexport class MatrixDropdownColumnBuilder extends DefaultColumnsBuilder {\n public buildColumns(questionBase: QuestionMatrixDropdownModel, table: Table): Array<IColumn> {\n const question = <QuestionMatrixDropdownModel>questionBase;\n const isDetailRowLocation = question.rows.length > 5;\n const columns = [];\n question.rows.forEach(row => {\n question.columns.forEach(col => {\n const column = new MatrixDropdownColumn(question, row, col, table);\n if(isDetailRowLocation) {\n column.location = QuestionLocation.Row;\n }\n columns.push(column);\n });\n });\n return columns;\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"matrixdropdown\", new MatrixDropdownColumnBuilder());\n\nexport class CustomColumnsBuilder extends DefaultColumnsBuilder<QuestionCustomModel> {\n protected createColumn(question: QuestionCustomModel, table: Table): CustomQuestionColumn {\n return new CustomQuestionColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"custom\", new CustomColumnsBuilder());\n\nexport class CompositeColumnsBuilder extends DefaultColumnsBuilder<QuestionCompositeModel> {\n public static ShowAsSeparateColumns = true;\n protected getDisplayName(question: QuestionCompositeModel, table: Table): string {\n return table.useNamesAsTitles\n ? question.name\n : (question.locTitle?.renderedHtml || question.title || \"\").trim() || question.name;\n }\n protected buildColumnsCore(question: QuestionCompositeModel, table: Table): Array<IColumn> {\n if(CompositeColumnsBuilder.ShowAsSeparateColumns) {\n const innerQuestions = [];\n question.contentPanel.addQuestionsToList(innerQuestions);\n let columns: Array<IColumn> = [];\n innerQuestions.forEach(innerQuestion => {\n const builder = ColumnsBuilderFactory.Instance.getColumnsBuilder(innerQuestion.getType());\n const cols = builder.buildColumns(innerQuestion, table);\n cols.forEach(col => {\n col.name = question.name + \".\" + col.name;\n col.displayName = this.getDisplayName(question, table) + \" - \" + this.getDisplayName(innerQuestion, table);\n });\n columns = columns.concat(cols);\n });\n return columns;\n }\n return super.buildColumnsCore(question, table);\n }\n protected createColumn(question: QuestionCompositeModel, table: Table): CompositeQuestionColumn {\n return new CompositeQuestionColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"composite\", new CompositeColumnsBuilder());\n\nexport class MatrixDynamicColumnsBuilder extends DefaultColumnsBuilder<QuestionMatrixDynamicModel> {\n protected createColumn(question: QuestionMatrixDynamicModel, table: Table): MatrixDynamicColumn {\n return new MatrixDynamicColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"matrixdynamic\", new MatrixDynamicColumnsBuilder());\n\nexport class PanelDynamicColumnsBuilder extends DefaultColumnsBuilder<QuestionPanelDynamicModel> {\n protected createColumn(question: QuestionPanelDynamicModel, table: Table): PanelDynamicColumn {\n return new PanelDynamicColumn(question, table);\n }\n}\nColumnsBuilderFactory.Instance.registerBuilderColumn(\"paneldynamic\", new PanelDynamicColumnsBuilder());\n","import { SurveyModel, Question, Event, Serializer, EventBase, surveyLocalization } from \"survey-core\";\nimport { hasLicense } from \"survey-core\";\nimport {\n IPermission,\n QuestionLocation,\n ITableState,\n IColumn,\n IColumnData,\n} from \"./config\";\nimport { Details } from \"./extensions/detailsextensions\";\nimport { localization } from \"../localizationManager\";\nimport { ITableExtension, TableExtensions } from \"./extensions/tableextensions\";\nimport { createCommercialLicenseLink } from \"../utils\";\nimport { ColumnsBuilderFactory } from \"./columnbuilder\";\nimport { DefaultColumn } from \"./columns\";\n\nexport interface ITableOptions {\n [index: string]: any;\n\n /**\n * Specifies whether to use question names instead of question titles as column headings.\n *\n * Default value: `false`\n */\n useNamesAsTitles?: boolean;\n /**\n * Specifies the delimiter used to separate multiple choice items in a list.\n *\n * Default value: `\", \"`\n */\n itemsDelimiter?: string;\n /**\n * A callback function that allows you to customize a question's display value in the table.\n *\n * Parameters:\n *\n * - `options.question`: `Question`\\\n * The question for which the callback is executed.\n * - `options.displayValue`: `any`\\\n * The question's display value. You can modify this parameter to change the output.\n */\n onGetQuestionValue?: (options: {\n question: Question,\n displayValue: any,\n }) => void;\n\n /**\n * Specifies the number of data items to load and display per page. Applies only if `paginationEnabled` is `true`.\n *\n * Default value: 10\n * @see paginationEnabled\n */\n pageSize?: number;\n /**\n * Specifies whether the dataset is split into pages.\n *\n * Default value: `true`\n *\n * > Pagination cannot be disabled if the dataset is loaded from a server (that is, if the second parameter passed to the `Tabulator` constructor is a function).\n * @see pageSize\n */\n paginationEnabled?: boolean;\n /**\n * Specifies whether responses to [Dynamic Matrix](https://surveyjs.io/form-library/examples/dynamic-matrix-add-new-rows/) and [Dynamic Panel](https://surveyjs.io/form-library/examples/duplicate-group-of-fields-in-form/) questions are rendered using nested tables.\n *\n * Default value: `true`\n *\n * If disabled, responses are displayed as stringified JSON objects instead of a tabular structure.\n */\n useNestedTables?: boolean;\n /**\n * Specifies whether to split responses to multi-select questions (Checkboxes and Multi-Select Dropdown) into separate columns.\n *\n * When enabled, each choice is represented as an individual column. Cell values indicate whether the choice was selected or the selection order, depending on the `multiSelectColumnValueFormat` setting. Empty cells indicate that the choice was not selected.\n *\n * Default value: `false`\n *\n */\n splitMultiSelectIntoColumns?: boolean;\n /**\n * Specifies how selected values are represented in columns generated from multi-select questions. Applies only when `splitMultiSelectIntoColumns` is `true`.\n *\n * Accepted values:\n *\n * - `\"checkmark\"` – Displays a checkmark symbol for selected choices.\n * - `\"selectionOrder\"` – Displays the order in which choices were selected (1, 2, 3, ...).\n *\n * Default value: `\"checkmark\"`\n */\n multiSelectColumnValueFormat?: \"checkmark\" | \"selectionOrder\";\n}\n\nexport type TabulatorFilter = { field: string, type: string, value: any };\nexport type TabulatorSortOrder = { field: string, direction: undefined | \"asc\" | \"desc\" };\nexport type GetDataUsingCallbackFn = (params: { filter?: Array<TabulatorFilter>, sort?: Array<TabulatorSortOrder>, offset?: number, limit?: number, callback?: (response: { data: Array<Object>, totalCount: number, error?: any }) => void }) => void;\nexport type GetDataUsingPromiseFn = (params: { filter?: Array<TabulatorFilter>, sort?: Array<TabulatorSortOrder>, offset?: number, limit?: number }) => Promise<{ data: Array<Object>, totalCount: number, error?: any }>;\nexport type GetDataFn = GetDataUsingCallbackFn | GetDataUsingPromiseFn;\n// export type GetDataFn = (params: { filter?: any, limit?: number, offset?: number, callback?: (response: { data: Array<Object>, total: number, error?: any }) => void }) => Promise<{ data: Array<Object>, total: number, error?: any }> | void;\n\nexport class TableEvent extends EventBase<Table> {}\n\nexport abstract class Table {\n public static showFilesAsImages = false;\n public static haveCommercialLicense: boolean = false;\n protected tableData: any;\n protected extensions: TableExtensions;\n private haveCommercialLicense = false;\n protected _columns: Array<IColumn>;\n constructor(\n protected _survey: SurveyModel,\n protected data: Array<Object> | GetDataFn,\n protected _options: ITableOptions = {},\n protected _columnsData: Array<IColumnData> = []\n ) {\n if(!this._options) {\n this._options = {};\n }\n if(typeof this._options.useNestedTables === \"undefined\") {\n this._options.useNestedTables = true;\n }\n\n this.initialize();\n\n this.extensions = new TableExtensions(this);\n const f = hasLicense;\n this.haveCommercialLicense = (!!f && f(4)) ||\n Table.haveCommercialLicense ||\n (!!_options &&\n (typeof _options.haveCommercialLicense !== \"undefined\"\n ? _options.haveCommercialLicense\n : false));\n }\n protected renderResult: HTMLElement;\n protected currentPageSize: number = 5;\n protected currentPageNumber: number;\n protected _rows: TableRow[] = [];\n protected isColumnReorderEnabled: boolean;\n\n public isInitialized = false;\n protected initialize(): void {\n this.isInitialized = false;\n this.currentPageSize = this.options.pageSize || 5;\n this.currentPageNumber = 1;\n this._columns = this.buildColumns(this._survey);\n this.initTableData(this.data);\n localization.currentLocale = this._survey.locale;\n\n if(this._columnsData.length !== 0) {\n this.updateColumnsFromData(this._columnsData);\n }\n this.isInitialized = true;\n }\n\n public getTableData(): Array<any> {\n return [].concat(this.tableData || []);\n }\n /**\n * Sets pagination selector content.\n */\n public paginationSizeSelector: number[] = [1, 5, 10, 25, 50, 100];\n\n public onColumnsVisibilityChanged = new TableEvent();\n\n public onColumnsLocationChanged = new TableEvent();\n\n public onRowRemoved = new TableEvent();\n\n public renderDetailActions: (\n container: HTMLElement,\n row: TableRow\n ) => HTMLElement;\n\n public getData() {\n return this.data;\n }\n public get survey() {\n return this._survey;\n }\n public get options() {\n return this._options;\n }\n\n public abstract applyFilter(value: string): void;\n public abstract applyColumnFilter(columnName: string, value: string): void;\n public abstract sortByColumn(columnName: string, direction: string): void;\n\n public render(targetNode: HTMLElement | string): void {\n if(typeof targetNode === \"string\") {\n targetNode = document.getElementById(targetNode);\n }\n targetNode.innerHTML = \"\";\n if(!this.haveCommercialLicense) {\n targetNode.appendChild(createCommercialLicenseLink());\n }\n }\n\n public enableColumnReorder() {\n this.isColumnReorderEnabled = true;\n }\n\n public disableColumnReorder(): void {\n this.isColumnReorderEnabled = false;\n }\n\n public getPageNumber(): number {\n return this.currentPageNumber;\n }\n\n public setPageNumber(value: number) {\n this.currentPageNumber = value;\n }\n\n /**\n * Returns current page size.\n */\n public getPageSize(): number {\n return this.currentPageSize;\n }\n\n /**\n * Sets current page size.\n */\n public setPageSize(value: number): void {\n this.currentPageSize = value;\n this.stateChanged();\n }\n\n public getCreatedRows(): TableRow[] {\n return [].concat(this._rows);\n }\n\n public clearCreatedRows(): void {\n this._rows.forEach((row) => {\n row.destroy();\n });\n this._rows = [];\n }\n\n public get useNamesAsTitles() {\n return this._options && this._options.useNamesAsTitles === true;\n }\n\n public get itemsDelimiter(): string {\n return this._options && this._options.itemsDelimiter || \", \";\n }\n\n protected buildColumns = (survey: SurveyModel) => {\n let columns: Array<IColumn> = [];\n this._survey.getAllQuestions().forEach((question: Question) => {\n if(!this.isNonValueQuestion(question)) {\n const builder = ColumnsBuilderFactory.Instance.getColumnsBuilder(question.getTemplate());\n columns = columns.concat(builder.buildColumns(question, this));\n }\n });\n return columns;\n };\n private isNonValueQuestion(question: Question): boolean {\n return Serializer.isDescendantOf(question.getType(), \"nonvalue\");\n }\n\n public isColumnVisible(column: IColumn) {\n if(column.location !== QuestionLocation.Column) return false;\n return column.isVisible;\n }\n\n public get columns