{"version":3,"file":"viewform.js","names":["offlineIdUrlParam","window","location","href","match","OFFLINEID","matrixQuestionId","matrixLineId","indexParam","isMatrix","isSurvey","formWidth","globalInit","selectedAction","matrixUndo","changedDrawings","matrixAttachments","matrixActions","uploadFileTrackers","matrixConfig","dataBound","resetMatrixHeader","resizable","selectable","change","handleMatrixSelect","matrixConnectedFormsConfig","filterable","gridFilterOptions","filterMenuInit","leanformsNext","updateSearchFilterMenu","sortable","datePickerMap","datepicker","datetimepicker","timepicker","typeIdMap","resetDatasetLinkbuttons","$","document","width","$body","on","e","keyCode","target","nodeName","attr","preventDefault","duplicateMatrixLine","documentElement","focus","isIosPWA","addClass","setTimeout","createUploads","FORMID","initActionsGrid","id","core","supportsOffline","hasClass","parent","hide","indexedDatabase","formTypeUsers","where","userId","USERID","formTypeBaseId","BASEID","first","formTypeOffline","toggleOfflineLabel","navigator","onLine","after","resources","datasetOfflineWarning","closeDropdownMenu","$this","$parent","find","removeClass","remove","off","toggleDropdownMenu","this","isOpen","loadPartIndex","createElement","insertAfter","scrollToElement","element","animationDuration","destinationOffset","offset","top","headerHeight","outerHeight","scrollContainer","newScrollTop","animate","scrollTop","partId","$part","$title","list","children","each","partTitle","text","el","li","append","openInfo","q","title","length","data","toFront","infoContent","html","css","kendoWindow","getPopupWidth","height","getPopupHeight","viewForm","extraInfo","visible","modal","close","deactivate","destroy","center","open","delForm","formControls","showOldConfirmDialog","confirmDeleteTitle","confirmDeleteMessage","dialogResult","serializedFormData","val","form","querySelector","setAttribute","baseUrl","FORMGUID","submit","toggleFavorite","stopImmediatePropagation","action","ajax","url","type","error","onAjaxError","success","toggleClass","label","currentText","newText","stepChanged","showRoute","win","routeTitle","iframe","content","reload","addEventListener","printHelper","before","needsHelperButtons","test","userAgent","body","input","prevAll","add","vanillaInput","get","cursorPosition","selectionStart","beforeCursor","slice","afterCursor","newCursorPosition","setSelectionRange","toggleCheckbox","a","b","prop","copyCheckbox","src","trg","beforesave","split","update","k","item","is","removeAttr","handleCopyCheckbox","copyFields","sourceId","targetList","isBeforeSave","sourceElement","sourceValue","targetIds","i","targetId","targetElement","loadData","isFieldCopy","name","value","handleCopy","attachmentDrawing","htmlEls","attachmentDrawingContainer","canvasElement","sizeHelper","undoButton","closeButton","confirmButton","colorButtons","defaultColorButton","eq","metaViewport","sidebarToggle","attachmentContainer","canvas","fabric","Canvas","renderOnAddRemove","selection","isDrawingMode","upperCanvasEl","freeDrawingBrush","openAttachmentDrawing","clear","resizeCanvas","passive","toggleZoomPrevention","setViewportTransform","imageElement","naturalWidth","naturalHeight","fabricImage","Image","setWidth","setHeight","renderAll","setColor","async","dataUriToBlob","dataUri","fetch","then","res","blob","closeDrawing","removeEventListener","closeAttachmentDrawing","hasDrawnPaths","getObjects","toDataURL","format","imageBlob","file","File","offlineId","wasSavedOffline","db","attachments","catch","err","handlePotentialQuotaError","handleError","previousState","wasSaved","previousGuid","filename","questionId","removeData","attachmentFormData","FormData","uploadUrl","method","uploadXHR","XMLHttpRequest","onUploadLoadFactory","onUploadError","send","container","onUploadLoad","status","uploaded","JSON","parse","response","newGuid","guid","link","displayName","newUrl","encodeURIComponent","colorButton","color","canvasWidth","getWidth","scale","zoom","getZoom","setDimensions","disableZoomPrevention","state","preventScale","hasPreventScale","includes","replace","toggle","lastItemIndex","previewAttachment","removeAttachment","button","previewContainer","isCopy","isNewAttachment","isSavedMatrix","fileName","URL","revokeObjectURL","delete","showConfirmDialog","confirmRemoveAttachment","removeCopiedAttachments","uploadInputs","forEach","isDisabled","kendoUpload","saveUrl","autoUpload","localization","select","attachmentsButtonText","dropFilesHere","attachmentsDropText","files","push","uid","showFileList","enabled","onUploadSuccess","getDisplayName","currentName","existingNames","map","attachment","checkName","isDuplicateName","indexOf","extensionIndex","lastIndexOf","extension","newName","operation","createPreviewElement","rawFile","sender","kendoFile","isImage","getOfflineUrl","previewEl","offline","options","step","offlineFormSaved","isDatasetValue","datasetField","buttons","drawOnAttachmentText","attachmentPreview","preview","img","endsWith","iconSuffix","getFileIconSuffix","icon","filenameText","download","currentStep","tracker","index","jElement","serviceWorker","message","copyForm","copyPopupTitle","FORMTYPEID","loadCopyData","dataToCopy","showMatrixAttachmentNotification","loadXml","notify","copySuccessMessage","matrixAttachmentCopyWarning","updateLock","release","noop","releaseLock","checkRequired","isValid","elements","querySelectorAll","isVisible","attachmentsDivId","getAttribute","attachentDiv","innerHTML","validation","showError","validationMessages","required","hideError","hasAttribute","resetButton","parentNode","resetButtonIsVisible","resetButtonIsEnabled","trim","closest","matrixIsVisible","matrixIsEnabled","checkDatasetUsername","callback","usernames","contentType","stringify","invalidUsername","isValidUsername","showUsernameDeclineDialog","xhr","readyState","username","usernameDeclinedWindow","datasetUsernameTexts","draggable","htmlEncode","buttonLabel","delayedResetMatrixHeader","thead","hideColumn","selected","rowNumber","isTotal","RegExp","wrapper","toggleMatrix","tableId","showText","hideText","$link","isAllVisible","table","matrixGrid","headerTable","$headerCell","showColumn","initMatrixPopup","openMatrixPopup","matrixFormTypeId","mode","dataInputId","dataInput","canEdit","params","matrix","lines","matrixDiv","join","popup","setOptions","getDisplayValue","questionEl","getElementById","isRadioButton","radioLabelId","radioLabel","textContent","tagName","toLowerCase","selectedOption","listId","checkboxLabelId","checkboxLabel","getMatrixLineData","line","lineFields","formData","serializeArray","questionIdRegex","filter","question","questionData","field","createElementNS","getMatrixLineAttachments","needsDrawingUpdate","substring","drawingData","datasetValue","json","setMatrixXMLToInput","jqXML","removeMatrixLine","parseInt","lineLocalAttachments","lineId","lineActions","getMatrixLineActions","confirmRemoveMatrixAction","oid","dataSource","refresh","customRemoveMatrixLine","hasOwnProperty","redrawMatrix","closeMatrixUndo","matrixLineDeletedText","matrixLineUndoButtonText","notification","duration","restoreMatrixLine","localAttachments","hideNotification","prependTo","newLine","clone","getNextLineId","empty","doNotCopyHeaders","header","fieldId","renewMatrixDialog","saveMatrixLine","keepOpen","toggleSaveButtons","isDataCorrector","validateForm","scrollOffset","customValidation","toggleLoadingOverlay","lineData","parentQuestionId","handleMatrixSave","application","lockScreen","kendo","ui","progress","idElement","parseXML","prepend","nextId","addMatrixLineXml","oldLine","replaceWith","addMatrixActions","actionGrid","clonedAction","extend","questionBase","questionNumber","getMatrixLineNumber","getMatrixAttachments","isOffline","fileData","attachmentData","toUpload","drawings","linesData","lineAttachments","isNew","toUploadData","metadata","isFromMatrix","localGuid","oldGuid","isImageDrawingUpdate","getUsedLists","listIds","selectedEl","selectedList","lists","sendForm","copyReadOnlyFields","usedLists","listsParam","actionsGrid","actionsData","_data","actionsParam","formFields","fullData","concat","saveFormOffline","serverData","fullMatrixAttachmentData","matrixAttachmentData","matrixDrawings","Object","keys","config","hasMissingFiles","hasChangedDrawings","isMissingAny","uploadMissingAttachments","completeAttachments","completeMatrixAttachments","saveFormOnNetwork","hasBeenSavedOnline","resetFormSaveUI","handleFormSaveError","arguments","setProgress","uploadRequests","completedUploads","missingGuids","request","createUploadRequest","drawing","matrixData","totalUploads","upload","abort","console","isDrawing","matrixAttachment","matrixAttachmentDataItem","undefined","Math","round","bind","attachmentsParam","matrixAttachmentsParam","fullStringData","param","dataType","savedId","showExportButtons","restartParam","restartId","confirmUrl","setIframeUrl","updatedMatrices","updatedMatrixId","drawingId","hoverCursor","forEachObject","fabricObject","set","saveSuccessMessage","codeDisplayEl","currentCode","code","attachmentPromises","previewElements","offlineAttachments","dbPromise","changedDrawingElements","hasOfflineId","put","flatMatrixAttachments","reduce","accu","updatePromise","Promise","all","attachmentIds","filesAmount","drawingsAmount","drawingsIndex","fileIds","drawingIds","matrixIds","formTitle","offlineData","formTypeId","baseId","createdAt","Date","now","transaction","forms","returnValue","syncOfflineData","savedOfflineMessage","inner","quotaErrorMessage","saveForm","forward","datasetUsernameValid","exportForm","downloadFile","serialize","defaultFormExport","percent","style","setProperty","resetRowNumber","renderNumber","loadActions","schema","model","fields","closedByName","description","initiatorId","initiatorName","planDate","remarks","selectedType","statusHtml","informGroupAllowUser","informGroup","transport","read","DataSource","grid","setDataSource","refreshActions","kendoGrid","columns","actionGridTexts","encoded","number","template","dataItem","dating","formatDate","formats","dateDisplayFormat","initiator","user","closedDate","closedBy","hidden","scrollable","firstCells","cell","kendoTooltip","position","refreshActionGrid","saveAction","usersInGroup","isClosed","selectedUser","parseDateString","stateClass","closedById","USERNAME","stateText","ok","statenotsaved","showAction","groupId","toggleActionButtons","closeActionDialog","editAction","grd","deleteAction","badge","parentQuestionNumber","lineNumber","badgeTitle","lineText","matrixLineNumber","toggleOffline","isAdd","installOffline","tryPersistIndexedDB","removeOffline","cacheUrl","markNetworkError","formTypes","lastChange","init","toolbar","matrices","typeSavedOfflineMessage","updateOfflineMenu","noNetworkMessage","typeRemovedOfflineMessage","storage","persist","handleOnChange","vanillaElement","checkMessage","vanillaElementId","substr","fillDependantList","calc","checkRights","changeDependentDate","dispatchEvent","Event","validateRangeState","dateHandleOnChange","dialogCounter","closeMessageDialog","messages","copyToQuestionBase","isFirstMessage","rawMessage","safeMessageHtml","sanitizeHtml","xmlDecode","xmlEncode","display","next","obj","initialized","copyToQuestion","msg","validateValueCompare","leftValue","operator","rightValue","msgWindow","actions","msgid","msgno","safeMessage","messageContainer","contents","messageTitle","setDateValue","_date","amount","amountfield","unit","fld","fieldAmount","selectedIndex","isNaN","addMinutes","addHours","addDays","addMonths","addYears","dateWidgetType","displayDate","getDate","getMonth","getFullYear","displayTime","pad","getHours","getMinutes","dateType","num","size","s","triggerEl","dateTriggers","isAmountField","dateFields","$sourceEl","targetField","$targetField","sourceBaseId","sourceType","sourceDate","datePickerDate","isDateStr","getDateFromString","vCacheEls","vCache","cached","Array","from","getFormulaValue","str","customDefaultValue","hasCustomDefaultValue","defaultValue","alternateDefaultValue","isArray","selectedRadio","radio","checked","checkedCheckboxes","checkbox","parseFloat","total","toString","optionAlt","numericVal","ex","formulaCache","triggerId","triggeredFormulas","formulaTriggers","callCalculate","formulaId","f","calculateFormula","result","eval","formula","handleFormulaError","decimals","currentVal","toFixed","initFormulaCache","initCalc","erroredFormulas","exception","formulaErrorMessage","rightActions","deny","allow","forcedDeny","validateRights","isCheckbox","triggeredRights","rights","checkedRights","right","matched","values","newAction","uniqueTargetId","targetType","toDelete","matchedRights","getValue","senderData","activeRights","affectedId","rightType","rightAction","affectedType","isPart","isQuestion","isPartDisabled","$trg","parentPart","matchedForcedDeny","forcedDenyRights","some","applyEvenOddParts","toggleQuestions","not","$q","questionTypes","radioButton","dropdown","toggleQuestion","dateTime","date","toggleDateInput","role","enable","matrixContainer","toggleAttachment","toggleCanvas","$canvas","o","isOdd","initRights","rightsInitOrder","rightsTrigger","lookupDataset","questionInput","loadDatasetRow","getDataset","formId","isNewForm","dialog","keyField","found","keyValue","trigger","isDataset","datasetData","keyFieldId","formGuid","closeDatasetDialog","canvasMaxWidth","canvases","onResize","xsWidth","parentPadding","formPadding","currentMaxWidth","parentWidth","wrapperEl","offsetWidth","ratio","getHeight","setBackground","rect","Rect","x","y","fill","stroke","strokeWidth","clearCanvas","createCanvas","readOnly","delayedCreateCanvas","canvasEl","$canvasEl","canDraw","hasDrawing","Boolean","canvasConfig","drawJSONOnCanvas","onCanvasMouseDown","onCanvasPathCreate","loadFromJSON","canvasLoaded","parseDrawing","jsonObject","unbind","lowerCanvasEl","allowDuplicateLine","lineCount","headerCells","hasCode","ri","duplicateButton","duplicateLabel","row","getMatrixCell","outerHTML","hasTotal","totalRow","totalLabel","isAverage","Number","th","$th","thid","vals","filePath","newTot","calculateTotal","mailA","appendChild","rel","noDrawing","currentTotal","formatIsXml","shouldRunEvents","missingDropdownOption","timeSpent","performance","att","fullMatrixXml","matrixLineXml","loadMatrixLocalAttachments","xmlData","datasetGuidInput","show","formDataUrl","formDataResult","formDataResultText","formDataXml","DOMParser","parseFromString","$formDataXml","startsWith","idx","md","questionValue","questionAttachmentsFromDataset","previousSibling","explanation","copiedAttachmentsOfflineExplanation","getElementsByName","localAttachment","isUnsavedMatrix","getCopiedFileObject","questionType","selectRadioId","$dropdown","optionExists","originalOption","removeDatasetAttachments","currentValue","previewCreatorFactory","parsedDate","datePickerType","parentElement","classList","contains","dataset","replicatedValue","linkButton","fileQuestionId","routeStep","optionNotFoundMessage","log","handleAfterLoad","fromDataset","array","newArray","offlineIds","previewOptions","loadOfflineAttachments","ids","anyOf","attachmentId","createObjectURL","setMatrixToolbar","formContainer","appendTo","tryLoadMatrixData","isNewMatrixLine","loadOfflineData","actionParam","loadFormData","eventBroadcaster","broadcast","broadcasts","FORM_SAVED_DATA_LOADED","openForm","guidInput","openConnectedFromMatrix"],"sources":["../../../scripts/leanforms/views/viewform.js"],"sourcesContent":["/*\r\n * Init\r\n */\r\nvar offlineIdUrlParam = window.location.href.match(/&offline-id=(\\d+)/);\r\nvar OFFLINEID = offlineIdUrlParam ? +offlineIdUrlParam[1] : -1;\r\n\r\n// Set by loadData, also used by attachment drawing.\r\nvar matrixQuestionId;\r\nvar matrixLineId;\r\n\r\nvar indexParam = window.location.href.match(/&index=(-?\\d+)/);\r\nvar isMatrix = !!indexParam;\r\nvar isSurvey = false;\r\n\r\nvar formWidth;\r\nvar globalInit = true;\r\nvar selectedAction = null;\r\nvar matrixUndo = null;\r\nvar changedDrawings = {};\r\nvar matrixAttachments = {};\r\nvar matrixActions = [];\r\n\r\n// Object to track files for each upload control.\r\nconst uploadFileTrackers = {};\r\n\r\nvar matrixConfig = {\r\n dataBound: resetMatrixHeader,\r\n resizable: true,\r\n selectable: true,\r\n change: handleMatrixSelect\r\n};\r\n\r\nvar matrixConnectedFormsConfig = {\r\n resizable: true,\r\n filterable: gridFilterOptions,\r\n filterMenuInit: leanformsNext.updateSearchFilterMenu,\r\n sortable: true,\r\n selectable: false \r\n};\r\n\r\nconst datePickerMap = {\r\n \"datepicker\": \"kendoDatePicker\",\r\n \"datetimepicker\": \"kendoDateTimePicker\",\r\n \"timepicker\": \"kendoTimePicker\"\r\n};\r\n\r\nconst typeIdMap = {\r\n \"0\": \"date\",\r\n \"1\": \"time\",\r\n \"2\": \"both\"\r\n};\r\n\r\nresetDatasetLinkbuttons();\r\n\r\n$(function () {\r\n formWidth = $(document).width();\r\n\r\n var $body = $(\"body\");\r\n\r\n $body.on(\"keydown\", function (e) {\r\n if (e.keyCode == 13 && (e.target.nodeName !== \"A\" && e.target.nodeName !== \"TEXTAREA\" && $(e.target).attr(\"data-role\") !== \"upload\")) {\r\n e.preventDefault();\r\n }\r\n });\r\n\r\n $(\".matrix-div\").on(\"click\", \".matrix-duplicate-button\", duplicateMatrixLine);\r\n document.documentElement.focus();\r\n\r\n if (leanformsNext.isIosPWA) {\r\n $body.addClass(\"is-ios-pwa\");\r\n }\r\n\r\n setTimeout(function () {\r\n createUploads();\r\n }, 0);\r\n\r\n if (FORMID !== \"-1\") {\r\n setTimeout(function () {\r\n initActionsGrid({ id: FORMID });\r\n }, 0);\r\n }\r\n\r\n if (!core.supportsOffline() || $body.hasClass(\"frame-master\")) {\r\n $(\"#btnToggleOffline\").parent().hide();\r\n }\r\n else {\r\n if (!isMatrix) {\r\n indexedDatabase.formTypeUsers\r\n .where({ userId: USERID, formTypeBaseId: +BASEID })\r\n .first(function (formTypeOffline) {\r\n if (formTypeOffline) {\r\n toggleOfflineLabel();\r\n }\r\n });\r\n }\r\n\r\n if (!window.navigator.onLine) {\r\n $(\"button.dataset\").parent().after(\"

\" + resources.datasetOfflineWarning + \"

\");\r\n }\r\n }\r\n});\r\n\r\n/**\r\n * Submenu\r\n */\r\nfunction closeDropdownMenu(e, $this, $parent) {\r\n $parent = $parent || $(\".btn-group.open\");\r\n $this = $this || $parent.find(\"button.dropdown-toggle\");\r\n\r\n $this.attr(\"aria-expanded\", \"true\")\r\n $parent.removeClass(\"open\");\r\n\r\n $(\".dropdown-backdrop\").remove();\r\n $(document).off(\"click\", closeDropdownMenu);\r\n}\r\n\r\nfunction toggleDropdownMenu() {\r\n var $this = $(this);\r\n var $parent = $this.parent();\r\n var isOpen = $parent.hasClass(\"open\");\r\n\r\n if (!isOpen) {\r\n if ($parent.hasClass(\"part-index-menu\")) {\r\n loadPartIndex();\r\n }\r\n\r\n if (\"ontouchstart\" in document.documentElement) {\r\n // If mobile we use a backdrop because click events don't delegate on iOS.\r\n $(document.createElement(\"div\"))\r\n .addClass(\"dropdown-backdrop\")\r\n .insertAfter($this)\r\n .on(\"click\", closeDropdownMenu);\r\n }\r\n\r\n closeDropdownMenu();\r\n\r\n $this.attr(\"aria-expanded\", \"true\")\r\n $parent.addClass(\"open\");\r\n\r\n setTimeout(function () {\r\n $(document).on(\"click\", closeDropdownMenu);\r\n }, 0);\r\n }\r\n else {\r\n closeDropdownMenu(null, $this, $parent);\r\n }\r\n}\r\n\r\n$(\".dropdown-toggle\").on(\"click\", toggleDropdownMenu);\r\n$(\".dropdown-menu a\").on(\"click\", function (e) {\r\n e.preventDefault();\r\n});\r\n\r\n/*\r\n * Part index menu\r\n */\r\nfunction scrollToElement(element, animationDuration) {\r\n var destinationOffset = element.offset().top;\r\n var headerHeight = $(\".js-fixed-header\").outerHeight();\r\n var scrollContainer = $(\"html, body\");\r\n var newScrollTop = destinationOffset - headerHeight;\r\n\r\n if (animationDuration > 0) {\r\n scrollContainer.animate({\r\n scrollTop: newScrollTop\r\n }, animationDuration);\r\n }\r\n else {\r\n scrollContainer.scrollTop(newScrollTop - 10);\r\n }\r\n}\r\n\r\n$(\".part-index-menu\").on(\"click\", \"a\", function (e) {\r\n e.preventDefault();\r\n\r\n var partId = $(this).attr(\"href\");\r\n var $part = $(partId);\r\n\r\n var $title = $part.find(\".formpart-title\");\r\n $title.removeClass(\"scroll-reached\");\r\n\r\n scrollToElement($part, 300);\r\n\r\n setTimeout(function () {\r\n $title.addClass(\"scroll-reached\");\r\n }, 200);\r\n});\r\n\r\nfunction loadPartIndex() {\r\n var list = $(\".part-index-list\");\r\n list.children(\":not(.dropdown-divider-header)\").remove();\r\n\r\n $(\".form .formpart\").each(function () {\r\n var $part = $(this);\r\n\r\n if ($part.hasClass(\"hidden\") || $part.hasClass(\"js-index-hidden\")) {\r\n return;\r\n }\r\n\r\n var partTitle = $part.find(\"h2\").text();\r\n var partId = $part.attr(\"id\");\r\n\r\n var el = $(\"\");\r\n el.attr(\"href\", \"#\" + partId);\r\n el.text(partTitle);\r\n\r\n var li = $(\"
  • \");\r\n li.append(el);\r\n\r\n list.append(li);\r\n })\r\n}\r\n\r\n/*\r\n * Misc UI actions\r\n */\r\nfunction openInfo(q, title) {\r\n if ($(\"#dialog_\" + q).length > 0) {\r\n $(\"#dialog_\" + q).data(\"kendoWindow\").toFront();\r\n return false;\r\n }\r\n\r\n // Sanitized server side.\r\n var infoContent = $(\"#info_\" + q).html();\r\n $(\"#windowcontainer\").append(\"
    \" + infoContent + \"
    \");\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n $(\"#dialog_\" + q).kendoWindow({\r\n width: leanformsNext.getPopupWidth(800),\r\n height: leanformsNext.getPopupHeight(650),\r\n title: `${resources.viewForm.extraInfo}${title}`,\r\n visible: false,\r\n modal: true,\r\n close: function () {\r\n $(\"body\").css(\"overflow\", \"\");\r\n },\r\n deactivate: function () {\r\n this.destroy();\r\n }\r\n }).data(\"kendoWindow\").center().open();\r\n}\r\n\r\nfunction delForm() {\r\n formControls.showOldConfirmDialog(\r\n resources.confirmDeleteTitle,\r\n resources.confirmDeleteMessage,\r\n function (dialogResult, serializedFormData) {\r\n if (dialogResult !== \"OK\") {\r\n return;\r\n }\r\n\r\n $(\"#deleteFormConfirmation\").val(serializedFormData);\r\n const form = document.querySelector(\"form\");\r\n form?.setAttribute(\"action\", `${baseUrl}/ViewForm?handler=Delete&FormGuid=${FORMGUID}`);\r\n form?.submit();\r\n }\r\n );\r\n}\r\n\r\nfunction toggleFavorite(e) {\r\n // Stop dropdown menu from closing.\r\n e.stopImmediatePropagation();\r\n\r\n var $this = $(this);\r\n var action = $this.find(\".k-icon\").hasClass(\"k-i-star-outline\") ? \"POST\" : \"DELETE\";\r\n\r\n $.ajax({\r\n url: baseUrl + \"/api/favorites?type=form&targetId=\" + BASEID,\r\n type: action,\r\n error: leanformsNext.onAjaxError,\r\n success: function () {\r\n $this.find(\".k-icon\").toggleClass(\"k-i-star k-i-star-outline\");\r\n\r\n var label = $this.find(\".favorite-label\");\r\n var currentText = label.text();\r\n var newText = label.data(\"toggle-label\");\r\n\r\n label.text(newText);\r\n label.data(\"toggle-label\", currentText);\r\n }\r\n });\r\n}\r\n\r\nvar stepChanged = false;\r\nfunction showRoute() {\r\n if (!$(\"#windowcontainer\").find(\"#dialog\").length) {\r\n var win = $(\"
    \");\r\n $(\"#windowcontainer\").append(win);\r\n }\r\n\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n\r\n $(\"#dialog\").kendoWindow({\r\n visible: false,\r\n height: leanformsNext.getPopupHeight(\"90%\"),\r\n width: leanformsNext.getPopupWidth(\"95%\"),\r\n title: resources.viewForm.routeTitle,\r\n modal: true,\r\n iframe: true,\r\n content: baseUrl + \"/FormViews/DesignRoute?FormGuid=\" + FORMGUID,\r\n resizable: true,\r\n close: function () {\r\n $(\"body\").css(\"overflow\", \"\");\r\n\r\n if (stepChanged) {\r\n location.reload();\r\n }\r\n },\r\n deactivate: function () {\r\n this.destroy();\r\n }\r\n }).data(\"kendoWindow\").center().open();\r\n}\r\n\r\nwindow.addEventListener(\"beforeprint\", function () {\r\n $(\".print-textarea\").remove();\r\n\r\n $(\".q-input textarea.form-control\").each(function () {\r\n var text = $(this).val();\r\n var printHelper = $(\"

    \").addClass(\"print-textarea form-control visible-print-block\").text(text);\r\n $(this).before(printHelper);\r\n });\r\n});\r\n\r\n/*\r\n * Numeric input touchscreen helper buttons.\r\n */\r\nvar needsHelperButtons = /Android|iPhone|iPod/i.test(window.navigator.userAgent);\r\n$(document.body).toggleClass(\"show-helper-buttons\", needsHelperButtons);\r\n$(\".numeric-helper-button\").on(\"click\", function () {\r\n var input = $(this).prevAll(\"input\");\r\n var add = $(this).text();\r\n var val = input.val();\r\n var vanillaInput = input.get(0)\r\n var cursorPosition = vanillaInput.selectionStart;\r\n var beforeCursor = val.slice(0, cursorPosition);\r\n var afterCursor = val.slice(cursorPosition);\r\n var newCursorPosition = cursorPosition + 1;\r\n\r\n input.val(beforeCursor + add + afterCursor).focus();\r\n vanillaInput.setSelectionRange(newCursorPosition, newCursorPosition);\r\n});\r\n\r\n/*\r\n * Checkbox un-check helper.\r\n */\r\nfunction toggleCheckbox(a, b) {\r\n $(\"#\" + b).prop(\"checked\", !$(a).prop(\"checked\"));\r\n}\r\n\r\n/*\r\n * Copy from other question.\r\n */\r\nfunction copyCheckbox(src, trg, beforesave) {\r\n var target = trg.split(\";\")\r\n var update;\r\n\r\n for (var k = 0; k < target.length; k++) {\r\n update = true;\r\n\r\n if (beforesave) {\r\n update = false;\r\n\r\n $(\"input[name^=\\\"\" + src + \"#\\\"]\").each(function () {\r\n var item = $(this).attr(\"name\").split(\"#\")[1];\r\n\r\n if ($(\"input[name^=\\\"\" + target[k] + \"#\" + item + \"\\\"]\").is(\":disabled\")) {\r\n $(\"input[name^=\\\"\" + target[k] + \"#\" + item + \"\\\"]\").removeAttr(\"disabled\");\r\n }\r\n\r\n update = true;\r\n });\r\n\r\n if ($(\"#id_\" + target[k]).is(\":disabled\")) {\r\n $(\"#id_\" + target[k]).removeAttr(\"disabled\");\r\n\r\n update = true;\r\n }\r\n }\r\n\r\n if (update) {\r\n $(\"input[name^=\\\"\" + src + \"#\\\"]\").each(function () {\r\n var item = $(this).attr(\"name\").split(\"#\")[1];\r\n $(\"input[name^=\\\"\" + target[k] + \"#\" + item + \"\\\"]\").prop(\"checked\", $(this).is(\":checked\"));\r\n });\r\n\r\n $(\"#id_\" + target[k]).prop(\"checked\", $(this).is(\":checked\"));\r\n }\r\n }\r\n}\r\n\r\nfunction handleCopyCheckbox(src, trg) {\r\n $(\"input[name^=\\\"\" + src + \"#\\\"]\").each(function () {\r\n $(this).change(function () {\r\n copyCheckbox(src, trg, false);\r\n })\r\n });\r\n\r\n $(\"#id_\" + src).change(function () {\r\n copyCheckbox(src, trg, false);\r\n })\r\n}\r\n\r\nfunction copyFields(sourceId, targetList, isBeforeSave) {\r\n var sourceElement = $(\"#id_\" + sourceId);\r\n var sourceValue = sourceElement.val();\r\n var targetIds = targetList.split(\";\");\r\n\r\n for (var i = 0; i < targetIds.length; i++) {\r\n var targetId = targetIds[i];\r\n var update = true;\r\n var targetElement = $(\"#id_\" + targetId);\r\n\r\n if (isBeforeSave) {\r\n if (targetElement.is(\":disabled\")) {\r\n targetElement.removeAttr(\"disabled\");\r\n }\r\n else {\r\n update = false;\r\n }\r\n }\r\n\r\n if (update) {\r\n loadData({\r\n isFieldCopy: true,\r\n data: [{\r\n name: targetId,\r\n value: sourceValue,\r\n sourceElement: sourceElement\r\n }]\r\n });\r\n }\r\n }\r\n}\r\n\r\nfunction handleCopy(sourceId, targetList) {\r\n $(\"#id_\" + sourceId).change(function () {\r\n copyFields(sourceId, targetList, false);\r\n });\r\n}\r\n\r\n/*\r\n * Drawing on attachment \r\n */\r\nvar attachmentDrawing = (function () {\r\n var htmlEls = $(document.documentElement).add(window.top.document.documentElement);\r\n var attachmentDrawingContainer = $(\".attachment-draw-container\");\r\n var canvasElement = $(\".attachment-draw-canvas\");\r\n var sizeHelper = $(\".attachment-draw-size-helper\");\r\n var undoButton = $(\".attachment-draw-undo\");\r\n var closeButton = $(\".attachment-draw-close\");\r\n var confirmButton = $(\".attachment-draw-confirm\");\r\n var colorButtons = $(\".attachment-draw-color\");\r\n var defaultColorButton = colorButtons.eq(0);\r\n var metaViewport = window.top.document.querySelector(\"meta[name=viewport]\");\r\n var sidebarToggle = window.top.$(\".resize-sidebar\");\r\n var attachmentContainer;\r\n\r\n var canvas = new fabric.Canvas(canvasElement.get(0), {\r\n renderOnAddRemove: false,\r\n selection: false,\r\n isDrawingMode: true\r\n });\r\n\r\n if (leanformsNext.isIosPWA) {\r\n // In iOS PWA, vertical movement would not follow the pointer correctly.\r\n // This is the only reliable fix I could find.\r\n canvas.upperCanvasEl.addEventListener(\"touchmove\", function (e) {\r\n e.preventDefault();\r\n });\r\n }\r\n\r\n canvas.freeDrawingBrush.width = 3;\r\n\r\n function openAttachmentDrawing() {\r\n canvas.clear();\r\n window.addEventListener(\"resize\", resizeCanvas, { passive: true });\r\n\r\n // Need to do this first to get correct dimensions.\r\n attachmentDrawingContainer.css(\"visibility\", \"hidden\");\r\n attachmentDrawingContainer.removeClass(\"hidden\");\r\n\r\n htmlEls.addClass(\"is-drawing-active\");\r\n toggleZoomPrevention(true);\r\n\r\n // Reset zoom so image size applies correctly.\r\n canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);\r\n\r\n attachmentContainer = $(this).parent().parent();\r\n\r\n var imageElement = attachmentContainer.find(\".attachment-item\").get(0);\r\n var width = imageElement.naturalWidth;\r\n var height = imageElement.naturalHeight;\r\n var fabricImage = new fabric.Image(imageElement, {\r\n width: width,\r\n height: height\r\n });\r\n\r\n sizeHelper.attr(\"src\", imageElement.src);\r\n\r\n canvas.setWidth(width);\r\n canvas.setHeight(height);\r\n\r\n canvas.add(fabricImage);\r\n canvas.renderAll();\r\n\r\n // We need some arbitrary timeout apparently.\r\n setTimeout(() => {\r\n resizeCanvas();\r\n\r\n attachmentDrawingContainer.css(\"visibility\", \"visible\");\r\n }, 100);\r\n\r\n setColor(defaultColorButton);\r\n }\r\n\r\n async function dataUriToBlob(dataUri) {\r\n return await fetch(dataUri).then(res => res.blob());\r\n }\r\n\r\n function closeDrawing() {\r\n attachmentDrawingContainer.addClass(\"hidden\");\r\n\r\n htmlEls.removeClass(\"is-drawing-active\");\r\n toggleZoomPrevention(false);\r\n\r\n window.removeEventListener(\"resize\", resizeCanvas, { passive: true });\r\n }\r\n\r\n async function closeAttachmentDrawing() {\r\n attachmentDrawingContainer.addClass(\"hidden\");\r\n\r\n htmlEls.removeClass(\"is-drawing-active\");\r\n toggleZoomPrevention(false);\r\n\r\n window.removeEventListener(\"resize\", resizeCanvas, { passive: true });\r\n\r\n // No need to upload when no (new) drawing is present.\r\n var hasDrawnPaths = canvas.getObjects().length > 1;\r\n if (!hasDrawnPaths) {\r\n return;\r\n }\r\n\r\n var dataUri = canvas.toDataURL({ format: \"jpeg\" });\r\n var imageBlob = await dataUriToBlob(dataUri);\r\n var file = new File([imageBlob], \"image-with-drawing.jpg\");\r\n\r\n attachmentContainer\r\n .find(\".attachment-item\")\r\n .attr(\"src\", dataUri);\r\n\r\n var offlineId = attachmentContainer.data(\"offline-id\");\r\n var wasSavedOffline = offlineId > 0;\r\n\r\n if (wasSavedOffline) {\r\n db.attachments.update(offlineId, {\r\n data: file,\r\n }).catch(function (err) {\r\n if (!handlePotentialQuotaError(err)) {\r\n leanformsNext.handleError(err);\r\n }\r\n });\r\n // TODO update link?\r\n\r\n return;\r\n }\r\n\r\n var previousState = attachmentContainer.data(\"state\");\r\n var wasSaved = previousState === \"saved\";\r\n var previousGuid = attachmentContainer.data(\"guid\");\r\n var filename = attachmentContainer.data(\"filename\");\r\n var questionId = attachmentContainer.data(\"question\");\r\n\r\n if (wasSaved) {\r\n // If immediate online save fails,\r\n // This is used by hasMissingAttachments() to retry on form save.\r\n attachmentContainer.data(\"needsDrawingUpdate\", true);\r\n }\r\n else {\r\n // Will make sure that save if retried on form save if it fails now.\r\n // This has a small chance of leaving orphaned attachments in the /temp/ folder.\r\n attachmentContainer.removeData(\"guid\");\r\n }\r\n\r\n attachmentContainer.data(\"file\", file);\r\n\r\n var attachmentFormData = new FormData();\r\n attachmentFormData.append(\"data\", file);\r\n\r\n var uploadUrl = baseUrl + \"/api/attachments/\" + questionId;\r\n var method;\r\n\r\n if (wasSaved) {\r\n if (isMatrix) {\r\n attachmentFormData.append(\"isFromMatrix\", true);\r\n attachmentFormData.append(\"matrixQuestionId\", matrixQuestionId);\r\n attachmentFormData.append(\"matrixLineId\", matrixLineId);\r\n }\r\n\r\n attachmentFormData.append(\"filename\", filename);\r\n attachmentFormData.append(\"formId\", FORMID);\r\n\r\n method = \"PUT\";\r\n }\r\n else {\r\n attachmentFormData.append(\"previousGuid\", previousGuid);\r\n\r\n method = \"POST\";\r\n }\r\n\r\n var uploadXHR = new XMLHttpRequest();\r\n\r\n uploadXHR.addEventListener(\"load\", onUploadLoadFactory(attachmentContainer, wasSaved), false);\r\n uploadXHR.addEventListener(\"error\", onUploadError, false);\r\n\r\n uploadXHR.open(method, uploadUrl, true);\r\n uploadXHR.send(attachmentFormData);\r\n }\r\n\r\n function onUploadLoadFactory(container, wasSaved) {\r\n return function onUploadLoad() {\r\n if (this.status !== 200) {\r\n leanformsNext.handleError(this);\r\n\r\n return;\r\n }\r\n\r\n var uploaded = JSON.parse(this.response);\r\n var newGuid = uploaded.guid;\r\n\r\n container.data(\"guid\", newGuid);\r\n\r\n var link = container.find(\"a\");\r\n var displayName = container.find(\".attachment-text\").text();\r\n var newUrl = baseUrl + uploaded.url + \"&displayName=\" + encodeURIComponent(displayName);\r\n\r\n link.attr(\"href\", newUrl);\r\n\r\n if (wasSaved) {\r\n container.data(\"filename\", uploaded.filename);\r\n container.removeData(\"needsDrawingUpdate\");\r\n\r\n if (isMatrix) {\r\n // TODO: Update matrix xml to make sure it keeps working even if they don't save the matrix line.\r\n }\r\n }\r\n }\r\n }\r\n\r\n function onUploadError() {\r\n // Error is likely due to being offline and can be ignored, save will be retried by hasMissingAttachments().\r\n // Download link may be wrong until that happens.\r\n }\r\n\r\n closeButton.on(\"click\", closeDrawing);\r\n confirmButton.on(\"click\", closeAttachmentDrawing);\r\n colorButtons.on(\"click\", function () {\r\n setColor($(this));\r\n });\r\n\r\n function setColor(colorButton) {\r\n colorButtons.removeClass(\"is-active\");\r\n colorButton.addClass(\"is-active\");\r\n\r\n var color = colorButton.css(\"backgroundColor\");\r\n\r\n canvas.freeDrawingBrush.color = color;\r\n }\r\n\r\n function resizeCanvas() {\r\n var width = sizeHelper.width();\r\n var height = sizeHelper.height();\r\n var canvasWidth = canvas.getWidth();\r\n\r\n var scale = width / canvasWidth;\r\n var zoom = canvas.getZoom();\r\n\r\n zoom *= scale;\r\n\r\n canvas.freeDrawingBrush.width = 3 / zoom;\r\n canvas.setDimensions({ width: width, height: height });\r\n canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);\r\n }\r\n\r\n var disableZoomPrevention = false;\r\n\r\n function toggleZoomPrevention(state) {\r\n if (disableZoomPrevention) {\r\n return;\r\n }\r\n\r\n var preventScale = \", maximum-scale=1\";\r\n var hasPreventScale = metaViewport.content.includes(preventScale);\r\n\r\n if (state && hasPreventScale) {\r\n // Is iPhone, where zoom should always be prevented due to automatic zooming.\r\n disableZoomPrevention = true;\r\n\r\n return;\r\n }\r\n\r\n if (state) {\r\n metaViewport.content += preventScale;\r\n } else if (!state) {\r\n metaViewport.content = metaViewport.content.replace(preventScale, \"\");\r\n }\r\n\r\n // Toggle also functions as forced repaint, required for meta viewport update to work.\r\n sidebarToggle.toggle(!state);\r\n }\r\n\r\n undoButton.on(\"click\", function () {\r\n var lastItemIndex = (canvas.getObjects().length - 1);\r\n var item = canvas.item(lastItemIndex);\r\n\r\n if (item.get(\"type\") === \"path\") {\r\n canvas.remove(item);\r\n canvas.renderAll();\r\n }\r\n });\r\n\r\n return {\r\n open: openAttachmentDrawing\r\n };\r\n})();\r\n\r\n/*\r\n * Attachments\r\n */\r\n$(\".form\").on(\"click\", \"button.preview-attachment\", leanformsNext.previewAttachment);\r\n$(\".form\").on(\"click\", \"button.delete-attachment\", removeAttachment);\r\n$(\".form\").on(\"click\", \"button.draw-attachment\", attachmentDrawing.open);\r\n\r\nfunction removeAttachment() {\r\n var button = $(this);\r\n var previewContainer = button.parent().parent();\r\n\r\n var questionId = previewContainer.data(\"question\");\r\n var guid = previewContainer.data(\"guid\");\r\n var isCopy = previewContainer.data(\"isCopy\");\r\n var isNewAttachment = previewContainer.data(\"state\") === \"new\";\r\n var isSavedMatrix = !isNewAttachment && FORMGUID === \"00000000-0000-0000-0000-000000000000\" && parent.FORMGUID;\r\n\r\n if (isNewAttachment) {\r\n var offlineId = previewContainer.data(\"offline-id\");\r\n var url = previewContainer.find(\"a\").attr(\"href\");\r\n\r\n previewContainer.remove();\r\n\r\n if (guid) {\r\n $.ajax({\r\n url: baseUrl + \"/api/attachments/temp\",\r\n type: \"DELETE\",\r\n data: { fileName: guid },\r\n error: leanformsNext.onAjaxError,\r\n success: function () { }\r\n });\r\n }\r\n else if (typeof offlineId === \"number\" && offlineId > -1) {\r\n if (url) {\r\n URL.revokeObjectURL(url);\r\n }\r\n\r\n indexedDatabase.attachments.delete(offlineId).catch(leanformsNext.handleError);\r\n }\r\n }\r\n else if (isSavedMatrix) {\r\n previewContainer.remove();\r\n }\r\n else {\r\n var fileName = previewContainer.data(\"filename\");\r\n\r\n formControls.showConfirmDialog(resources.confirmRemoveAttachment, function () {\r\n if (isCopy && FORMGUID === \"00000000-0000-0000-0000-000000000000\") {\r\n removeCopiedAttachments(questionId, fileName);\r\n previewContainer.remove();\r\n }\r\n else {\r\n $.ajax({\r\n url: baseUrl + \"/api/attachments\",\r\n type: \"DELETE\",\r\n data: {\r\n fileName: fileName,\r\n guid: FORMGUID,\r\n questionId: questionId\r\n },\r\n error: leanformsNext.onAjaxError,\r\n success: function () {\r\n previewContainer.remove();\r\n }\r\n });\r\n }\r\n });\r\n }\r\n}\r\n\r\nfunction createUploads() {\r\n uploadInputs.forEach(function (questionId) { \r\n var input = $(\"#id_\" + questionId);\r\n var isDisabled = input.prop(\"disabled\");\r\n\r\n // In initialize file upload tracker array.\r\n uploadFileTrackers[questionId] = [];\r\n\r\n input.kendoUpload({\r\n async: {\r\n saveUrl: baseUrl + \"/api/attachments/\" + questionId,\r\n autoUpload: true\r\n },\r\n localization: {\r\n select: resources.attachmentsButtonText,\r\n dropFilesHere: resources.attachmentsDropText\r\n },\r\n select: function (e) {\r\n // Store selected files in the order the upload control returns them (so we can maintain order when rendering previews).\r\n e.files.forEach(file => uploadFileTrackers[questionId].push(file.uid));\r\n },\r\n showFileList: false,\r\n enabled: !isDisabled,\r\n success: onUploadSuccess,\r\n error: onUploadError\r\n });\r\n });\r\n}\r\n\r\nfunction getDisplayName(currentName, questionId) {\r\n var existingNames = $.map(\r\n $(\"#attachments_\" + questionId).find(\".attachment-container\"), function (attachment) {\r\n return $(attachment).find(\".attachment-text\").text();\r\n }\r\n );\r\n\r\n function checkName(name, i) {\r\n var isDuplicateName = existingNames.indexOf(name) > -1;\r\n if (!isDuplicateName) {\r\n return name;\r\n }\r\n\r\n var extensionIndex = name.lastIndexOf(\".\");\r\n var fileName = extensionIndex > -1 ? name.slice(0, extensionIndex) : name;\r\n var extension = extensionIndex > -1 ? name.slice(extensionIndex) : \"\";\r\n\r\n var newName = \"\";\r\n\r\n if (!i) {\r\n i = 1;\r\n newName = fileName + \" (1)\" + extension;\r\n }\r\n else {\r\n newName = fileName.replace(/^(.*)( \\(\\d+\\))$/, \"$1 (\" + i + \")\") + extension;\r\n }\r\n\r\n return checkName(newName, i + 1);\r\n }\r\n\r\n return checkName(currentName);\r\n}\r\n\r\nfunction onUploadSuccess(e) {\r\n if (e.operation !== \"upload\") {\r\n return;\r\n }\r\n\r\n const questionId = this.name;\r\n\r\n e.files.forEach(function (file) {\r\n var displayName = getDisplayName(e.response.displayName, questionId);\r\n\r\n createPreviewElement({\r\n url: baseUrl + e.response.url + \"&displayName=\" + encodeURIComponent(displayName),\r\n displayName: displayName,\r\n questionId: questionId,\r\n guid: e.response.guid,\r\n uid: file.uid,\r\n file: file.rawFile,\r\n state: \"new\"\r\n });\r\n });\r\n}\r\n\r\nfunction onUploadError(e) {\r\n var response = e.XMLHttpRequest;\r\n\r\n if (FORMID !== \"-1\" || e.operation !== \"upload\" || response.status !== 0 || !core.supportsOffline()) {\r\n // TODO: show error.\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n\r\n var questionId = e.sender.name;\r\n var kendoFile = e.files[0];\r\n var fileName = kendoFile.name;\r\n var file = kendoFile.rawFile;\r\n var displayName = getDisplayName(fileName, questionId);\r\n var url = \"\";\r\n\r\n if (formControls.isImage(fileName)) {\r\n url = getOfflineUrl(file);\r\n }\r\n\r\n var previewEl = createPreviewElement({\r\n url: url,\r\n displayName: displayName,\r\n questionId: questionId,\r\n file: file,\r\n uid: kendoFile.uid,\r\n offline: true,\r\n state: \"new\"\r\n });\r\n\r\n if (isMatrix) {\r\n return;\r\n }\r\n\r\n indexedDatabase.attachments.add({\r\n type: \"file\",\r\n data: file,\r\n displayName: displayName,\r\n questionId: questionId\r\n }).then(function (id) {\r\n previewEl.data(\"offline-id\", id);\r\n });\r\n}\r\n\r\nfunction createPreviewElement(options) {\r\n var src = options.url;\r\n var offlineId = options.offlineId;\r\n var fileName = options.fileName;\r\n var displayName = options.displayName;\r\n var guid = options.guid;\r\n var file = options.file;\r\n var questionId = options.questionId;\r\n var state = options.state;\r\n var step = options.step;\r\n var offlineFormSaved = options.offlineFormSaved;\r\n var isDatasetValue = options.isDatasetValue;\r\n var isCopy = options.isCopy;\r\n var datasetField = options.datasetField;\r\n\r\n var previewContainer = $(\"#attachments_\" + questionId);\r\n\r\n if (!previewContainer.length) {\r\n var canvas = $(\"#C_\" + questionId);\r\n\r\n canvas\r\n .data(\"state\", state)\r\n .data(\"filename\", fileName);\r\n\r\n return;\r\n }\r\n\r\n var container = $(\"

    \")\r\n .data(\"state\", state)\r\n .data(\"question\", questionId);\r\n\r\n if (guid) {\r\n container.data(\"guid\", guid);\r\n }\r\n\r\n if (file) {\r\n container.data(\"file\", file);\r\n }\r\n\r\n if (isDatasetValue) {\r\n container.data(\"isDatasetValue\", isDatasetValue);\r\n }\r\n\r\n if (isCopy) {\r\n container.data(\"isCopy\", isCopy);\r\n }\r\n\r\n if (datasetField) {\r\n container.data(\"datasetField\", datasetField);\r\n }\r\n\r\n if (fileName) {\r\n container.data(\"filename\", fileName);\r\n }\r\n\r\n if (step) {\r\n container.data(\"step\", step);\r\n }\r\n\r\n if (offlineFormSaved) {\r\n container.data(\"offline-saved\", offlineFormSaved);\r\n }\r\n\r\n if (typeof offlineId !== \"undefined\") {\r\n container.data(\"offline-id\", offlineId);\r\n }\r\n\r\n if (formControls.isImage(displayName)) {\r\n var buttons = $(\"
    \");\r\n\r\n if (!isDatasetValue && !isCopy) {\r\n buttons.append(``);\r\n }\r\n\r\n buttons.append(``);\r\n container.append(buttons);\r\n\r\n var img = $(\"\").attr(\"src\", src);\r\n container.append(img);\r\n }\r\n else if (displayName.endsWith(\"pdf\")) {\r\n var buttons = $(\"
    \");\r\n buttons.append(``);\r\n container.append(buttons);\r\n\r\n container.append(\"
    \");\r\n }\r\n else {\r\n var iconSuffix = formControls.getFileIconSuffix(displayName);\r\n var icon = $(\"
    \").addClass(\"k-i-file\" + iconSuffix);\r\n\r\n container.append(icon);\r\n }\r\n\r\n var filenameText = displayName.replace(/^\\d+_\\d+_(.*)/, \"$1\");\r\n var text = $(\"
    \")\r\n .append(\"\")\r\n .append($(``).attr(\"href\", src).text(filenameText));\r\n\r\n var button = ``;\r\n if (state === \"new\" || step === currentStep || currentStep === \"-1\") {\r\n text.append(button);\r\n }\r\n\r\n container.append(text);\r\n\r\n // We want to maintain order when we have a uid (new uplaod).\r\n if (options.uid) {\r\n const tracker = uploadFileTrackers[questionId];\r\n const index = tracker?.indexOf(options.uid);\r\n const children = previewContainer.find(\".attachment-container:data(index)\");\r\n \r\n // Add the intended position on the container.\r\n container.data(\"index\", index);\r\n\r\n // Just add to the end of the previewContainer; when a loaded form, when first, or when actual in a larger position requested.\r\n if (index > children.length || children.length === 0) {\r\n previewContainer.append(container);\r\n }\r\n else {\r\n // Insert the new element before the child at the target index.\r\n for (let element of children) {\r\n const jElement = $(element); \r\n if (jElement.data(\"index\") > index) {\r\n jElement.before(container);\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n else {\r\n previewContainer.append(container);\r\n }\r\n\r\n return container;\r\n}\r\n\r\nif (core.supportsOffline()) {\r\n window.navigator.serviceWorker.addEventListener(\"message\", function (e) {\r\n if (e.data.message === \"sync-complete\") {\r\n // Sync removes attachments that don't have a related form.\r\n // Offline saved attachments which haven't been saved to the form will also be included in this.\r\n $(\".attachment-container\").each(function () {\r\n var preview = $(this);\r\n\r\n if (preview.data(\"offline-id\") > 0 && preview.data(\"offline-saved\") !== true) {\r\n preview.removeData(\"offline-id\");\r\n }\r\n });\r\n }\r\n });\r\n}\r\n\r\n/*\r\n * Copy from other form.\r\n */\r\nfunction copyForm() {\r\n $(\"#windowcontainer\").append(\"
    \");\r\n\r\n const width = formWidth < 900 ? \"95%\" : \"50%\";\r\n\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n $(\"#copydialog\").kendoWindow({\r\n width: leanformsNext.getPopupWidth(width),\r\n height: leanformsNext.getPopupHeight(\"90%\"),\r\n title: resources.copyPopupTitle,\r\n visible: false,\r\n modal: true,\r\n iframe: true,\r\n close: function () {\r\n $(\"body\").css(\"overflow\", \"\");\r\n },\r\n deactivate: function () {\r\n this.destroy();\r\n },\r\n content: baseUrl + \"/CopyForm?formtype=\" + FORMTYPEID + \"&mode=data\"\r\n }).data(\"kendoWindow\").center().open();\r\n}\r\n\r\nfunction loadCopyData(dataToCopy, showMatrixAttachmentNotification) {\r\n if (dataToCopy != \"\") {\r\n loadData({\r\n isCopy: true,\r\n data: leanformsNext.loadXml(dataToCopy)\r\n });\r\n }\r\n\r\n $(\"#copydialog\").data(\"kendoWindow\").close();\r\n core.notify(\"success\", resources.copySuccessMessage);\r\n\r\n if (showMatrixAttachmentNotification) {\r\n core.notify(\"warn\", resources.viewForm.matrixAttachmentCopyWarning);\r\n }\r\n}\r\n\r\n/*\r\n * Lock\r\n */\r\nfunction updateLock(release) {\r\n $.ajax({\r\n url: baseUrl + \"/api/forms/\" + FORMID + \"/lock\",\r\n type: release ? \"DELETE\" : \"PUT\",\r\n async: release ? false : true,\r\n success: $.noop\r\n });\r\n}\r\n\r\nfunction releaseLock() {\r\n updateLock(true);\r\n}\r\n\r\n/*\r\n * Required validation.\r\n */\r\nfunction checkRequired() {\r\n let isValid = true;\r\n\r\n // Validate file inputs.\r\n let elements = document.querySelectorAll(\"input[required][type=file]\");\r\n [...elements].forEach((element) => {\r\n if (core.isVisible(element)) {\r\n const attachmentsDivId = element.getAttribute(\"id\").replace(\"id_\", \"attachments_\");\r\n const attachentDiv = document.querySelector(`#${attachmentsDivId}`);\r\n if (!attachentDiv.innerHTML) {\r\n isValid = validation.showError(element, resources.validationMessages.required);\r\n }\r\n else {\r\n validation.hideError(element);\r\n }\r\n }\r\n });\r\n\r\n // Parse canvas.\r\n elements = document.querySelectorAll(\"textarea[required][iscanvas]\");\r\n [...elements].forEach((element) => {\r\n if (!element.hasAttribute(\"disabled\")) {\r\n try {\r\n var resetButton = element.parentNode.querySelector(\".clearcanvas\");\r\n var resetButtonIsVisible = core.isVisible(resetButton);\r\n var resetButtonIsEnabled = !resetButton.hasAttribute(\"disabled\");\r\n\r\n if (resetButtonIsVisible && resetButtonIsEnabled && element.value.trim() === \"\") {\r\n isValid = validation.showError(element, resources.validationMessages.required);\r\n }\r\n else {\r\n validation.hideError(element);\r\n }\r\n } catch (e) { }\r\n }\r\n });\r\n\r\n // Parse matrix.\r\n elements = document.querySelectorAll(\"textarea[required][type=matrix]\");\r\n [...elements].forEach((element) => {\r\n if (!element.hasAttribute(\"disabled\")) {\r\n try {\r\n var container = element.closest(\".matrix-div\");\r\n var matrixIsVisible = core.isVisible(container);\r\n var matrixIsEnabled = core.isVisible(container.querySelector(\".matrixAdd\"));\r\n\r\n if (matrixIsVisible && matrixIsEnabled && element.value.indexOf(\"line id\") < 0) {\r\n isValid = validation.showError(element, resources.validationMessages.required);\r\n }\r\n else {\r\n validation.hideError(element);\r\n }\r\n } catch (e) { }\r\n }\r\n });\r\n\r\n return isValid;\r\n}\r\n\r\n/*\r\n * Dataset username validation.\r\n */\r\nfunction checkDatasetUsername(callback) {\r\n var usernames = $(\".is-dataset-username\").map(function () {\r\n var val = $(this).val();\r\n\r\n if (val) {\r\n return val;\r\n }\r\n }).get();\r\n\r\n if (!usernames.length) {\r\n return callback(true);\r\n }\r\n\r\n $.ajax({\r\n url: baseUrl + \"/api/users/check-usernames\",\r\n type: \"POST\",\r\n contentType: \"application/json\",\r\n data: JSON.stringify(usernames),\r\n success: function (invalidUsername) {\r\n var isValidUsername = !invalidUsername;\r\n\r\n if (!isValidUsername) {\r\n showUsernameDeclineDialog(invalidUsername);\r\n }\r\n\r\n callback(isValidUsername);\r\n },\r\n error: function (xhr) {\r\n if (xhr.readyState !== 0) {\r\n return leanformsNext.onAjaxError(xhr);\r\n }\r\n\r\n // Likely offline, let forward continue without checking.\r\n callback(true);\r\n }\r\n });\r\n}\r\n\r\nfunction showUsernameDeclineDialog(username) {\r\n $('#windowcontainer').append('
    ');\r\n\r\n var usernameDeclinedWindow = $(\"#usernamedecline-dialog\").kendoWindow({\r\n title: resources.datasetUsernameTexts.title,\r\n width: leanformsNext.getPopupWidth(440),\r\n visible: false,\r\n modal: true,\r\n resizable: false,\r\n draggable: false,\r\n deactivate: function () {\r\n this.destroy();\r\n }\r\n }).data(\"kendoWindow\");\r\n\r\n usernameDeclinedWindow.content(\r\n \"
    \" +\r\n \"
    \" + resources.datasetUsernameTexts.body.replace(\"{0}\", core.htmlEncode(username)) + \"
    \" +\r\n \"
    \" +\r\n \" \" +\r\n \"
    \" +\r\n \"
    \"\r\n );\r\n\r\n $(usernameDeclinedWindow.element).find(\"#btnOk\").on(\"click\", function () {\r\n usernameDeclinedWindow.close();\r\n });\r\n usernameDeclinedWindow.center().open();\r\n}\r\n\r\n/*\r\n * Matrix.\r\n */\r\nfunction resetMatrixHeader(e) {\r\n $(function () {\r\n setTimeout(function delayedResetMatrixHeader() {\r\n e.sender.thead.find(\"th\").each(function (index) {\r\n var el = $(this);\r\n\r\n if (el.hasClass(\"HiddenColumn\") || el.hasClass(\"rights-hidden-column\")) {\r\n e.sender.hideColumn(index);\r\n }\r\n });\r\n }, 0);\r\n });\r\n}\r\n\r\nfunction handleMatrixSelect(e) {\r\n var selected = this.select();\r\n var rowNumber = selected.children().first().text();\r\n var isTotal = !new RegExp(\"\\\\d+\").test(rowNumber);\r\n\r\n if (isTotal) {\r\n selected.removeClass(\"k-selected\");\r\n }\r\n\r\n this.wrapper.parent().find(\".matrixRemove, .matrixEdit, .matrixView\").prop(\"disabled\", isTotal);\r\n}\r\n\r\nfunction toggleMatrix(link, tableId, showText, hideText) {\r\n var $link = $(link);\r\n var isAllVisible = $link.data(\"showing-all\");\r\n\r\n var table = $(\"#\" + tableId);\r\n var matrixGrid = table.data(\"kendoGrid\");\r\n var headerTable = table.closest(\".matrix-div\").find(\".k-grid-header-table\");\r\n\r\n headerTable.find(isAllVisible ? \".VisibleColumn\" : \".HiddenColumn\").each(function () {\r\n var $headerCell = $(this);\r\n var index = $headerCell.index();\r\n\r\n if (isAllVisible) {\r\n matrixGrid.hideColumn(index);\r\n } else {\r\n matrixGrid.showColumn(index);\r\n }\r\n\r\n $headerCell.toggleClass(\"VisibleColumn HiddenColumn\");\r\n });\r\n\r\n $link\r\n .text(isAllVisible ? showText : hideText)\r\n .data(\"showing-all\", !isAllVisible);\r\n}\r\n\r\nfunction initMatrixPopup(container) {\r\n container.append(\"\");\r\n\r\n container.kendoWindow({\r\n visible: false,\r\n height: leanformsNext.getPopupHeight(700),\r\n width: leanformsNext.getPopupWidth(900),\r\n title: \"Matrix\",\r\n modal: true,\r\n close: function () {\r\n $(\"body\").css(\"overflow\", \"\");\r\n },\r\n deactivate: function () {\r\n $(\"#iMatrixForm\").attr(\"src\", \"about:blank\");\r\n }\r\n });\r\n};\r\n\r\nfunction openMatrixPopup(questionId, matrixFormTypeId, mode) {\r\n var dataInputId = \"md_\" + questionId;\r\n var dataInput = $(\"#\" + dataInputId);\r\n\r\n var canEdit = mode === \"new\" || dataInput.prevAll(\".matrixEdit\").is(\":visible\");\r\n var selected = $(\"#id_\" + questionId + \" tr.k-selected\").index();\r\n var indexParam = \"index=\" + (mode === \"edit\" && typeof selected === \"number\" ? selected : -1);\r\n\r\n var params = [\r\n \"FormTypeId=\" + matrixFormTypeId,\r\n\r\n // ParentFormTypeId is used only by service worker.\r\n \"ParentFormTypeId=\" + BASEID,\r\n\r\n // can-edit-question is used by server and service worker.\r\n \"can-edit-question=\" + canEdit,\r\n\r\n // Also used by server to determine that this is a matrix.\r\n \"parent-question=\" + questionId,\r\n indexParam\r\n ];\r\n\r\n if (mode !== \"new\" && selected > -1) {\r\n var matrix = leanformsNext.loadXml(\"\" + dataInput.val() + \"\");\r\n var lines = $(matrix).find(\"line\");\r\n\r\n params.push(\"lineid=\" + lines.eq(selected).attr(\"id\"));\r\n }\r\n\r\n if (FORMGUID !== \"00000000-0000-0000-0000-000000000000\") {\r\n params.push(\"parent-guid=\" + FORMGUID);\r\n }\r\n\r\n var matrixDiv = $(\"#matrixDiv\");\r\n\r\n if (!matrixDiv.data(\"kendoWindow\")) {\r\n initMatrixPopup(matrixDiv);\r\n }\r\n\r\n // Needs to be after initMatrixPopup.\r\n var link = baseUrl + \"/ViewForm?\" + params.join(\"&\");\r\n $(\"#iMatrixForm\").attr(\"src\", link);\r\n\r\n // Avoid scrolling outer body when popup is open.\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n\r\n var popup = matrixDiv.data(\"kendoWindow\");\r\n popup.setOptions({\r\n height: leanformsNext.getPopupHeight(700)\r\n });\r\n popup.center().open();\r\n}\r\n\r\nfunction getDisplayValue(questionId, value) {\r\n var questionEl = document.getElementById(\"id_\" + questionId);\r\n var isRadioButton = false;\r\n\r\n if (!questionEl) {\r\n questionEl = document.getElementById(\"id_\" + questionId + \"_\" + value);\r\n isRadioButton = true;\r\n }\r\n\r\n if (!questionEl) {\r\n return {\r\n value: value\r\n };\r\n }\r\n\r\n if (isRadioButton) {\r\n var radioLabelId = questionEl.getAttribute(\"aria-labelledby\");\r\n var radioLabel = document.getElementById(radioLabelId);\r\n\r\n return {\r\n value: radioLabel.textContent\r\n };\r\n }\r\n\r\n var tagName = questionEl.tagName.toLowerCase();\r\n var type = questionEl.type;\r\n\r\n if (tagName === \"select\") {\r\n var selectedOption = $(questionEl).find(\":selected\");\r\n var listId = selectedOption.data(\"list\");\r\n\r\n return {\r\n value: selectedOption.text(),\r\n listId: listId\r\n };\r\n }\r\n\r\n if (tagName === \"input\" && type === \"checkbox\") {\r\n var checkboxLabelId = questionEl.getAttribute(\"aria-labelledby\");\r\n if (!checkboxLabelId) {\r\n return {\r\n value: \"[X]\"\r\n };\r\n }\r\n\r\n var checkboxLabel = document.getElementById(checkboxLabelId);\r\n\r\n return {\r\n value: checkboxLabel.textContent\r\n };\r\n }\r\n\r\n return {\r\n value: value\r\n };\r\n}\r\n\r\nfunction getMatrixLineData() {\r\n var line = leanformsNext.loadXml(\"\");\r\n var lineFields = line.find(\"linefields\");\r\n\r\n var formData = $(\".main-form\").serializeArray();\r\n\r\n // Id's are numerical, contain # for check boxes.\r\n var questionIdRegex = /^(ds_)?[0-9#]+$/;\r\n\r\n formData\r\n .filter(function (question) {\r\n return questionIdRegex.test(question.name);\r\n })\r\n .forEach(function (question) {\r\n var questionId = question.name;\r\n var value = question.value;\r\n var questionData = getDisplayValue(questionId, value);\r\n\r\n // Create element this way to avoid jQuery adding namespace attribute, which breaks some server side parsing.\r\n var field = $(document.createElementNS(null, \"field\"));\r\n field.attr(\"vraagid\", questionId);\r\n field.attr(\"value\", value);\r\n field.attr(\"display\", questionData.value);\r\n\r\n if (questionData.listId) {\r\n field.attr(\"list\", questionData.listId);\r\n }\r\n\r\n lineFields.append(field);\r\n });\r\n\r\n return line.find(\"line\");\r\n}\r\n\r\nfunction getMatrixLineAttachments() {\r\n var attachments = [];\r\n\r\n $(\".attachment-container\").each(function () {\r\n var file = $(this);\r\n\r\n var state = file.data(\"state\");\r\n var rawFile = file.data(\"file\");\r\n var needsDrawingUpdate = file.data(\"needsDrawingUpdate\");\r\n var offlineId = file.data(\"offline-id\");\r\n var guid = file.data(\"guid\");\r\n var fileName = file.data(\"filename\");\r\n var step = file.data(\"step\");\r\n var isDatasetValue = file.data(\"isDatasetValue\");\r\n var isCopy = file.data(\"isCopy\");\r\n var datasetField = file.data(\"datasetField\");\r\n var questionId = file.data(\"question\");\r\n var displayName = file.find(\".attachment-text\").text();\r\n\r\n attachments.push({\r\n type: \"file\",\r\n state: state,\r\n data: rawFile,\r\n needsDrawingUpdate: needsDrawingUpdate,\r\n offlineId: offlineId,\r\n guid: guid,\r\n fileName: fileName,\r\n step: step,\r\n isDatasetValue: isDatasetValue,\r\n isCopy: isCopy,\r\n datasetField: datasetField,\r\n displayName: displayName,\r\n questionId: questionId\r\n });\r\n });\r\n\r\n $(\".q-input .canvas-container\").each(function () {\r\n var container = $(this);\r\n var canvas = container.find(\".lower-canvas\");\r\n\r\n var questionId = canvas.attr(\"id\").substring(2);\r\n var state = canvas.data(\"state\");\r\n var offlineId = canvas.data(\"offline-id\");\r\n var guid = canvas.data(\"guid\");\r\n var fileName = canvas.data(\"filename\");\r\n var isDatasetValue = false;\r\n var datasetField = null;\r\n var drawingData = changedDrawings[questionId];\r\n\r\n if (state !== \"saved\" && !drawingData && typeof offlineId === \"undefined\") {\r\n return;\r\n }\r\n\r\n var datasetValue = $(\"#file_\" + questionId).val();\r\n if (datasetValue && datasetValue != \"\") {\r\n var json = JSON.parse(datasetValue);\r\n fileName = json.fileName;\r\n datasetField = json.datasetField;\r\n }\r\n\r\n attachments.push({\r\n type: \"drawing\",\r\n state: state,\r\n data: drawingData,\r\n offlineId: offlineId,\r\n guid: guid,\r\n fileName: fileName,\r\n questionId: questionId,\r\n datasetField: datasetField\r\n });\r\n });\r\n\r\n return attachments;\r\n}\r\n\r\nfunction setMatrixXMLToInput(input, jqXML) {\r\n input.val(jqXML.find(\"matrix\").html());\r\n}\r\n\r\nfunction removeMatrixLine(questionId) {\r\n var dataInputId = \"md_\" + questionId;\r\n var dataInput = $(\"#\" + dataInputId);\r\n var matrix = leanformsNext.loadXml(\"\" + dataInput.val() + \"\");\r\n var index = parseInt($(\"#id_\" + questionId + \" .k-selected td:first\").html()) - 1;\r\n var line = matrix.find(\"line\")[index];\r\n var lineLocalAttachments = [];\r\n var lineId = +$(line).attr(\"id\");\r\n var lineActions = getMatrixLineActions(questionId, lineId);\r\n\r\n if (lineActions && lineActions.length > 0) {\r\n formControls.showConfirmDialog(resources.confirmRemoveMatrixAction, function () {\r\n lineActions.forEach(function (action) {\r\n if (action.oid !== -1) {\r\n $.ajax({\r\n url: baseUrl + \"/api/actions/\" + action.oid,\r\n method: \"DELETE\",\r\n success: function () { },\r\n error: leanformsNext.onAjaxError\r\n });\r\n }\r\n\r\n $(\"#grdActions\").data(\"kendoGrid\").dataSource.remove(action);\r\n $(\"#grdActions\").data(\"kendoGrid\").refresh();\r\n });\r\n\r\n removeMatrixLine(questionId);\r\n });\r\n\r\n return;\r\n }\r\n\r\n try {\r\n customRemoveMatrixLine(line, baseUrl);\r\n }\r\n catch (e) { }\r\n\r\n if (matrixAttachments.hasOwnProperty(questionId)) {\r\n lineLocalAttachments = matrixAttachments[questionId][lineId];\r\n delete matrixAttachments[questionId][lineId];\r\n }\r\n\r\n $(line).remove();\r\n\r\n setMatrixXMLToInput(dataInput, matrix);\r\n redrawMatrix(dataInputId);\r\n\r\n closeMatrixUndo();\r\n\r\n var content = resources.matrixLineDeletedText + \"   \";\r\n var notification = core.notify(\"warn\", content, { duration: 8 * 1000 });\r\n notification.querySelector(\".matrix-undo-button\").addEventListener(\"click\", restoreMatrixLine, false);\r\n\r\n matrixUndo = {\r\n notification: notification,\r\n questionId: questionId,\r\n index: index,\r\n line: line,\r\n localAttachments: lineLocalAttachments\r\n };\r\n}\r\n\r\nfunction closeMatrixUndo() {\r\n if (matrixUndo && matrixUndo.notification) {\r\n core.hideNotification(matrixUndo.notification);\r\n matrixUndo = null;\r\n }\r\n}\r\n\r\nfunction restoreMatrixLine() {\r\n if (!matrixUndo) {\r\n return;\r\n }\r\n\r\n core.hideNotification(matrixUndo.notification);\r\n\r\n var questionId = matrixUndo.questionId;\r\n var index = matrixUndo.index;\r\n var line = $(matrixUndo.line);\r\n var localAttachments = matrixUndo.localAttachments;\r\n\r\n var lineId = +line.attr(\"id\");\r\n\r\n if (matrixAttachments.hasOwnProperty(questionId)) {\r\n matrixAttachments[questionId][lineId] = localAttachments;\r\n }\r\n\r\n var dataInputId = \"md_\" + questionId;\r\n var dataInput = $(\"#\" + dataInputId);\r\n var matrix = leanformsNext.loadXml(\"\" + dataInput.val() + \"\");\r\n var lines = matrix.find(\"line\");\r\n\r\n if (index === 0) {\r\n line.prependTo(matrix.find(\"matrix\"));\r\n }\r\n else {\r\n line.insertAfter(lines.eq(index - 1));\r\n }\r\n\r\n setMatrixXMLToInput(dataInput, matrix);\r\n redrawMatrix(dataInputId);\r\n matrixUndo = null;\r\n}\r\n\r\nfunction duplicateMatrixLine() {\r\n var $this = $(this);\r\n var index = $this.closest(\"tr\").index();\r\n var questionId = $this.closest(\"table\").attr(\"name\");\r\n\r\n var dataInputId = \"md_\" + questionId;\r\n var dataInput = $(\"#\" + dataInputId);\r\n var matrix = leanformsNext.loadXml(\"\" + dataInput.val() + \"\");\r\n var line = matrix.find(\"line\").eq(index);\r\n\r\n var newLine = line.clone();\r\n newLine.attr(\"id\", getNextLineId(matrix));\r\n newLine.children(\"attachments\").empty();\r\n\r\n // Remove every question data that has attribute 'data-donot-copy' in its header.\r\n const doNotCopyHeaders = this.closest(\".matrix-div\").querySelectorAll(\"th[data-donot-copy]\");\r\n doNotCopyHeaders.forEach((header) => {\r\n const fieldId = header.getAttribute(\"fldid\");\r\n\r\n // Remove from newline.\r\n newLine.find(\"field[vraagid=\" + fieldId + \"]\").remove();\r\n });\r\n\r\n line.after(newLine);\r\n\r\n setMatrixXMLToInput(dataInput, matrix);\r\n redrawMatrix(dataInputId);\r\n}\r\n\r\nfunction renewMatrixDialog() {\r\n var newUrl = $(\"#iMatrixForm\").attr(\"src\")\r\n .replace(/&index=\\d+/, \"&index=-1\")\r\n .replace(/&lineid=\\d*/, \"&lineid=\");\r\n\r\n $(\"#iMatrixForm\").attr(\"src\", newUrl);\r\n}\r\n\r\n// This is called inside the matrix iframe.\r\nfunction saveMatrixLine(keepOpen) {\r\n toggleSaveButtons(false);\r\n\r\n if (isDataCorrector || validation.validateForm({ container: document, scrollOffset: 100, customValidation: checkRequired })) {\r\n toggleLoadingOverlay(true);\r\n\r\n // TODO: This was only at save, not at save new.\r\n $(\"[disabled]:not(button)\").removeAttr(\"disabled\");\r\n\r\n var lineData = getMatrixLineData();\r\n var attachments = getMatrixLineAttachments();\r\n\r\n var index = window.location.href.match(/&index=(-?\\d+)/)[1];\r\n var parentQuestionId = window.location.href.match(/&parent-question=(\\d+)/)[1];\r\n\r\n parent.handleMatrixSave(keepOpen, lineData, attachments, index, parentQuestionId);\r\n }\r\n else {\r\n toggleSaveButtons(true);\r\n }\r\n}\r\n\r\nfunction toggleLoadingOverlay(state) {\r\n typeof application !== \"undefined\" ? application.lockScreen(state) : kendo.ui.progress($(document.body), state);\r\n}\r\n\r\nfunction getNextLineId(matrix) {\r\n var idElement = matrix.find('id');\r\n if (!idElement.length) {\r\n // TODO: IE is no longer supported - can this be simplified? 'Creating with parseXML is required for IE'.\r\n idElement = $($.parseXML(\"\")).find(\"id\");\r\n matrix.find(\"matrix\").prepend(idElement);\r\n }\r\n\r\n var nextId = +idElement.attr(\"value\");\r\n idElement.attr(\"value\", nextId + 1);\r\n\r\n return nextId;\r\n}\r\n\r\nfunction addMatrixLineXml(lineData, index, dataInput) {\r\n var matrix = leanformsNext.loadXml(\"\" + dataInput.val() + \"\");\r\n matrix.find(\"matrix\").append(lineData);\r\n\r\n var lines = matrix.find(\"line\");\r\n var newLine = lines.eq(lines.length - 1);\r\n var id;\r\n\r\n if (index === \"-1\") {\r\n id = getNextLineId(matrix);\r\n }\r\n else {\r\n var oldLine = lines.eq(index);\r\n id = oldLine.attr(\"id\");\r\n oldLine.replaceWith(newLine);\r\n }\r\n\r\n newLine.attr(\"id\", id);\r\n setMatrixXMLToInput(dataInput, matrix);\r\n\r\n return +id;\r\n}\r\n\r\nfunction resetDatasetLinkbuttons() {\r\n $(\".dataset\").each(function () {\r\n $(\"#btn_\" + this.getAttribute(\"name\")).hide();\r\n });\r\n}\r\n\r\n// This is called from the matrix popup.\r\nfunction handleMatrixSave(keepOpen, lineData, attachments, index, parentQuestionId) {\r\n closeMatrixUndo();\r\n\r\n var dataInputId = \"md_\" + parentQuestionId;\r\n var dataInput = $(\"#\" + dataInputId);\r\n var questionId = +dataInput.attr(\"name\");\r\n\r\n var lineId = addMatrixLineXml(lineData, index, dataInput);\r\n if (!matrixAttachments.hasOwnProperty(questionId)) {\r\n matrixAttachments[questionId] = {};\r\n }\r\n\r\n matrixAttachments[questionId][lineId] = attachments;\r\n if (keepOpen) {\r\n renewMatrixDialog();\r\n }\r\n else {\r\n $(\"#matrixDiv\").data(\"kendoWindow\").close();\r\n }\r\n\r\n redrawMatrix(dataInputId);\r\n addMatrixActions(lineId);\r\n\r\n toggleLoadingOverlay(false);\r\n\r\n // We have added a line so we can remove any validation errors (for now).\r\n validation.hideError(dataInput?.get(0));\r\n}\r\n\r\nfunction addMatrixActions(lineId) {\r\n initActionsGrid();\r\n\r\n const actionGrid = $(\"#grdActions\").data(\"kendoGrid\");\r\n matrixActions.forEach((action) => {\r\n // Kendo does not add the object to the datasource if not cloned!!\r\n const clonedAction = $.extend({}, action);\r\n\r\n clonedAction.lineId = lineId;\r\n clonedAction.questionBase = clonedAction.questionNumber;\r\n clonedAction.questionNumber = getMatrixLineNumber(clonedAction);\r\n actionGrid.dataSource.add(clonedAction);\r\n });\r\n\r\n matrixActions = [];\r\n}\r\n\r\n/*\r\n * Saving\r\n */\r\nfunction getMatrixAttachments(isOffline, attachments, fileData) {\r\n var attachmentData = [];\r\n var toUpload = [];\r\n var drawings = [];\r\n\r\n for (var matrixQuestionId in matrixAttachments) {\r\n var lines = matrixAttachments[matrixQuestionId];\r\n var linesData = [];\r\n\r\n for (var lineId in lines) {\r\n var lineAttachments = lines[lineId];\r\n var line = {\r\n id: lineId,\r\n attachments: []\r\n };\r\n\r\n lineAttachments.forEach(function (attachment) {\r\n var isNew = attachment.state !== \"saved\";\r\n var needsDrawingUpdate = attachment.needsDrawingUpdate;\r\n var questionId = attachment.questionId;\r\n var step = attachment.step;\r\n var isDatasetValue = attachment.isDatasetValue;\r\n var datasetField = attachment.datasetField;\r\n var fileName = attachment.fileName;\r\n var rawFile = attachment.data;\r\n var guid = attachment.guid;\r\n var displayName = attachment.displayName;\r\n var offlineId = attachment.offlineId;\r\n var toUploadData;\r\n\r\n if (attachment.type === \"drawing\") {\r\n if (!isNew) {\r\n line.attachments.push({\r\n type: \"drawing\",\r\n state: \"saved\",\r\n fileName: fileName,\r\n questionId: questionId,\r\n isDatasetValue: isDatasetValue,\r\n datasetField: datasetField\r\n });\r\n }\r\n else {\r\n if (!isOffline) {\r\n drawings.push({\r\n data: rawFile,\r\n questionId: questionId,\r\n matrixQuestionId: matrixQuestionId,\r\n lineId: lineId\r\n });\r\n }\r\n\r\n var data = {\r\n type: \"drawing\",\r\n questionId: questionId\r\n };\r\n\r\n if (isOffline) {\r\n data.data = rawFile;\r\n data.offlineId = offlineId;\r\n data.metadata = {\r\n isFromMatrix: true,\r\n matrixQuestionId: matrixQuestionId,\r\n lineId: lineId\r\n };\r\n }\r\n\r\n line.attachments.push(data);\r\n }\r\n\r\n return;\r\n }\r\n\r\n if (isNew) {\r\n if (isOffline) {\r\n line.attachments.push({\r\n type: \"file\",\r\n data: rawFile,\r\n displayName: displayName,\r\n questionId: questionId,\r\n offlineId: offlineId,\r\n metadata: {\r\n isFromMatrix: true,\r\n matrixQuestionId: matrixQuestionId,\r\n lineId: lineId\r\n }\r\n });\r\n }\r\n else if (guid) {\r\n attachments[guid] = {\r\n questionId: questionId,\r\n displayName: displayName,\r\n isFromMatrix: true,\r\n matrixQuestionId: matrixQuestionId,\r\n lineId: lineId\r\n };\r\n\r\n fileData[guid] = rawFile;\r\n\r\n line.attachments.push({\r\n guid: guid,\r\n displayName: displayName,\r\n questionId: questionId\r\n });\r\n }\r\n else {\r\n var localGuid = kendo.guid();\r\n attachment.guid = localGuid;\r\n\r\n line.attachments.push({\r\n guid: localGuid,\r\n displayName: displayName,\r\n questionId: questionId\r\n });\r\n\r\n toUploadData = {\r\n oldGuid: localGuid,\r\n questionId: questionId,\r\n displayName: displayName,\r\n file: rawFile,\r\n isFromMatrix: true,\r\n matrixQuestionId: matrixQuestionId,\r\n lineId: lineId\r\n };\r\n }\r\n }\r\n else {\r\n line.attachments.push({\r\n state: \"saved\",\r\n step: step,\r\n fileName: fileName,\r\n displayName: displayName,\r\n questionId: questionId,\r\n isDatasetValue: isDatasetValue,\r\n datasetField: datasetField\r\n });\r\n\r\n if (needsDrawingUpdate) {\r\n var localGuid = kendo.guid();\r\n attachment.guid = localGuid;\r\n\r\n toUploadData = {\r\n isImageDrawingUpdate: true,\r\n oldGuid: localGuid,\r\n questionId: questionId,\r\n file: rawFile,\r\n filename: fileName,\r\n isFromMatrix: true,\r\n matrixQuestionId: matrixQuestionId,\r\n lineId: lineId\r\n };\r\n }\r\n }\r\n\r\n if (toUploadData) {\r\n toUpload.push(toUploadData);\r\n }\r\n });\r\n\r\n if (lineAttachments.length) {\r\n linesData.push(line);\r\n }\r\n }\r\n\r\n if (linesData.length) {\r\n attachmentData.push({\r\n questionId: matrixQuestionId,\r\n lines: linesData\r\n });\r\n }\r\n }\r\n\r\n return {\r\n attachmentData: attachmentData,\r\n toUpload: toUpload,\r\n drawings: drawings\r\n };\r\n}\r\n\r\nfunction getUsedLists() {\r\n var listIds = [];\r\n\r\n $(\".form-content select\").each(function () {\r\n var selectedEl = $(this).find(\":selected\");\r\n var selectedList = selectedEl.data(\"list\");\r\n\r\n if (selectedList) {\r\n listIds.push(this.id + \"=\" + selectedList);\r\n }\r\n });\r\n\r\n var lists = listIds.length > 0 ? \"l_\" + listIds.join(\",l_\") : \"\";\r\n\r\n return lists;\r\n}\r\n\r\nfunction sendForm(action, callback) {\r\n toggleLoadingOverlay(true);\r\n closeMatrixUndo();\r\n\r\n copyReadOnlyFields();\r\n\r\n const usedLists = getUsedLists();\r\n const listsParam = usedLists !== \"\" ? [{ name: \"lists\", value: usedLists }] : [];\r\n\r\n const actionsGrid = $(\"#grdActions\").data(\"kendoGrid\");\r\n const actionsData = actionsGrid ? JSON.stringify(actionsGrid._data) || \"\" : \"\";\r\n const actionsParam = [{ name: \"actions\", value: actionsData }];\r\n\r\n const formFields = $(\".main-form\").serializeArray();\r\n const fullData = formFields.concat(listsParam, actionsParam);\r\n\r\n if (OFFLINEID !== -1) {\r\n saveFormOffline(action, fullData);\r\n return;\r\n }\r\n\r\n let attachments = {};\r\n let fileData = {};\r\n let toUpload = [];\r\n\r\n $(\".attachment-container\").each(function () {\r\n var file = $(this);\r\n var state = file.data(\"state\");\r\n var needsDrawingUpdate = file.data(\"needsDrawingUpdate\");\r\n\r\n if (state !== \"new\" && !needsDrawingUpdate) {\r\n return;\r\n }\r\n\r\n var guid = file.data(\"guid\");\r\n var questionId = file.data(\"question\");\r\n var rawFile = file.data(\"file\");\r\n var filename = file.data(\"filename\");\r\n var displayName = file.find(\".attachment-text\").text();\r\n\r\n var serverData = {\r\n questionId: questionId,\r\n displayName: displayName\r\n };\r\n\r\n if (guid && !needsDrawingUpdate) {\r\n attachments[guid] = serverData;\r\n fileData[guid] = rawFile;\r\n }\r\n else {\r\n var localGuid = kendo.guid();\r\n file.data(\"guid\", localGuid);\r\n\r\n serverData.oldGuid = localGuid;\r\n serverData.file = rawFile;\r\n\r\n if (needsDrawingUpdate) {\r\n serverData.isImageDrawingUpdate = true;\r\n serverData.filename = filename;\r\n }\r\n\r\n toUpload.push(serverData);\r\n }\r\n });\r\n\r\n // Mutates attachments variable.\r\n var fullMatrixAttachmentData = getMatrixAttachments(false, attachments, fileData);\r\n var matrixAttachmentData = fullMatrixAttachmentData.attachmentData;\r\n var matrixDrawings = fullMatrixAttachmentData.drawings;\r\n toUpload = toUpload.concat(fullMatrixAttachmentData.toUpload);\r\n\r\n // Checks if uploaded attachments are still there.\r\n var formData = new FormData();\r\n formData.append(\"guids\", JSON.stringify(Object.keys(attachments)));\r\n core.fetch({\r\n url: baseUrl + \"/api/attachments/check\",\r\n config: {\r\n body: formData,\r\n method: \"POST\"\r\n }\r\n })\r\n .then((response) => response.json())\r\n .then((response) => {\r\n var hasMissingFiles = Object.keys(toUpload).length;\r\n var hasChangedDrawings = Object.keys(changedDrawings).length;\r\n var isMissingAny = response.isMissingAny || hasMissingFiles || hasChangedDrawings || matrixDrawings.length;\r\n\r\n if (isMissingAny) {\r\n uploadMissingAttachments(attachments, fileData, matrixAttachmentData, matrixDrawings, response, toUpload, function (completeAttachments, completeMatrixAttachments) {\r\n saveFormOnNetwork(fullData, completeAttachments, completeMatrixAttachments, action, callback);\r\n });\r\n }\r\n else {\r\n saveFormOnNetwork(fullData, attachments, matrixAttachmentData, action, callback);\r\n }\r\n }).catch((error) => {\r\n toggleSaveButtons(true);\r\n\r\n var hasBeenSavedOnline = FORMID !== \"-1\";\r\n\r\n if (hasBeenSavedOnline && !window.navigator.onLine) {\r\n resetFormSaveUI();\r\n return core.notify(\"warn\", resources.hasBeenSavedOnline, { duration: 0 });\r\n }\r\n\r\n if (!core.supportsOffline()) {\r\n return handleFormSaveError(arguments, this);\r\n }\r\n\r\n saveFormOffline(action, fullData);\r\n });\r\n}\r\n\r\nfunction uploadMissingAttachments(attachments, fileData, matrixAttachmentData, matrixDrawings, response, toUpload, callback) {\r\n setProgress(0);\r\n\r\n var uploadRequests = [];\r\n var completedUploads = 0;\r\n\r\n if (response.isMissingAny) {\r\n response.missingGuids.forEach(function (guid) {\r\n var serverData = attachments[guid];\r\n serverData.oldGuid = guid;\r\n serverData.file = fileData[guid];\r\n\r\n toUpload.push(serverData);\r\n\r\n delete attachments[guid];\r\n });\r\n }\r\n\r\n toUpload.forEach(function (file) {\r\n var metadata = {};\r\n\r\n if (file.isFromMatrix) {\r\n metadata = {\r\n isFromMatrix: true,\r\n matrixQuestionId: file.matrixQuestionId,\r\n lineId: file.lineId\r\n };\r\n }\r\n\r\n if (file.isImageDrawingUpdate) {\r\n metadata.isImageDrawingUpdate = true;\r\n metadata.filename = file.filename;\r\n }\r\n\r\n metadata.oldGuid = file.oldGuid;\r\n metadata.displayName = file.displayName;\r\n\r\n var request = createUploadRequest(file.questionId, file.file, false, file.isImageDrawingUpdate, metadata);\r\n uploadRequests.push(request);\r\n });\r\n\r\n for (var questionId in changedDrawings) {\r\n var drawingData = changedDrawings[questionId];\r\n\r\n uploadRequests.push(createUploadRequest(questionId, drawingData, true));\r\n }\r\n\r\n matrixDrawings.forEach(function (drawing) {\r\n var questionId = drawing.questionId;\r\n var drawingData = drawing.data;\r\n var matrixData = {\r\n isFromMatrix: true,\r\n matrixQuestionId: drawing.matrixQuestionId,\r\n lineId: drawing.lineId\r\n };\r\n\r\n uploadRequests.push(createUploadRequest(questionId, drawingData, true, false, matrixData));\r\n });\r\n\r\n var totalUploads = uploadRequests.length;\r\n\r\n uploadRequests.forEach(function (upload) {\r\n upload.send();\r\n });\r\n\r\n function onUploadLoad() {\r\n if (this.status !== 200) {\r\n handleFormSaveError(this);\r\n\r\n uploadRequests.forEach(function (upload) {\r\n try {\r\n upload.abort();\r\n }\r\n catch (err) {\r\n console.error(\"Could not abort upload after error.\", err);\r\n }\r\n });\r\n\r\n return;\r\n }\r\n\r\n var uploaded = JSON.parse(this.response);\r\n var metadata = uploaded.metadata || {};\r\n\r\n var newGuid = uploaded.guid;\r\n var oldGuid = metadata.oldGuid;\r\n var displayName = metadata.displayName;\r\n var isImageDrawingUpdate = metadata.isImageDrawingUpdate;\r\n var questionId = uploaded.questionId;\r\n var isDrawing = uploaded.type === \"drawing\";\r\n\r\n if (!isImageDrawingUpdate) {\r\n attachments[newGuid] = {\r\n questionId: questionId\r\n };\r\n }\r\n\r\n if (metadata.isFromMatrix) {\r\n var matrixQuestionId = metadata.matrixQuestionId;\r\n var lineId = metadata.lineId;\r\n\r\n if (isDrawing) {\r\n attachments[newGuid].isFromMatrix = true;\r\n\r\n matrixAttachments[matrixQuestionId][lineId]\r\n .find(function (attachment) {\r\n return +attachment.questionId === questionId;\r\n })\r\n .guid = newGuid;\r\n\r\n matrixAttachmentData\r\n .find(function (matrix) {\r\n return matrix.questionId === matrixQuestionId;\r\n })\r\n .lines\r\n .find(function (line) {\r\n return line.id === lineId;\r\n })\r\n .attachments\r\n .find(function (attachment) {\r\n return +attachment.questionId === questionId;\r\n })\r\n .guid = newGuid;\r\n }\r\n else {\r\n if (!isImageDrawingUpdate) {\r\n var attachment = attachments[newGuid];\r\n attachment.isFromMatrix = true;\r\n attachment.displayName = displayName;\r\n }\r\n\r\n var matrixAttachment = matrixAttachments[matrixQuestionId][lineId]\r\n .find(function (attachment) {\r\n return attachment.guid === oldGuid;\r\n });\r\n\r\n var matrixAttachmentDataItem = matrixAttachmentData\r\n .find(function (matrix) {\r\n return matrix.questionId === matrixQuestionId;\r\n })\r\n .lines\r\n .find(function (line) {\r\n return line.id === lineId;\r\n })\r\n .attachments\r\n .find(function (attachment) {\r\n return attachment.guid === oldGuid;\r\n });\r\n\r\n if (isImageDrawingUpdate) {\r\n matrixAttachment.guid = undefined;\r\n matrixAttachment.needsDrawingUpdate = undefined;\r\n matrixAttachment.fileName = uploaded.filename;\r\n\r\n matrixAttachmentDataItem.fileName = uploaded.filename;\r\n }\r\n else {\r\n matrixAttachment.guid = newGuid;\r\n matrixAttachmentDataItem.guid = newGuid;\r\n }\r\n }\r\n }\r\n else if (!isDrawing) {\r\n $(\"#attachments_\" + questionId + \" .attachment-container\").each(function () {\r\n var preview = $(this);\r\n\r\n if (preview.data(\"guid\") === oldGuid) {\r\n if (isImageDrawingUpdate) {\r\n preview\r\n .removeData(\"guid\")\r\n .removeData(\"isDrawingUpdate\")\r\n .data(\"filename\", uploaded.filename);\r\n\r\n var link = preview.find(\"a\");\r\n var displayName = preview.find(\".attachment-text\").text();\r\n var newUrl = baseUrl + uploaded.url + \"&displayName=\" + encodeURIComponent(displayName);\r\n\r\n link.attr(\"href\", newUrl);\r\n }\r\n else {\r\n preview.data(\"guid\", newGuid);\r\n }\r\n\r\n // Stops loop.\r\n return false;\r\n }\r\n });\r\n\r\n if (!isImageDrawingUpdate) {\r\n attachments[newGuid].displayName = displayName;\r\n }\r\n }\r\n\r\n completedUploads++;\r\n\r\n setProgress(Math.round((completedUploads / totalUploads) * 100));\r\n\r\n if (completedUploads === totalUploads) {\r\n callback(attachments, matrixAttachmentData);\r\n }\r\n }\r\n\r\n function createUploadRequest(questionId, fileData, isDrawing, isImageDrawingUpdate, metadata) {\r\n var uploadXHR = new XMLHttpRequest();\r\n\r\n uploadXHR.addEventListener(\"load\", onUploadLoad, false);\r\n uploadXHR.addEventListener(\"error\", handleFormSaveError, false);\r\n\r\n var uploadUrl = baseUrl + \"/api/attachments/\" + questionId;\r\n var method = \"POST\";\r\n\r\n var attachmentFormData = new FormData();\r\n attachmentFormData.append(\"data\", fileData);\r\n attachmentFormData.append(\"metadata\", JSON.stringify(metadata));\r\n\r\n if (isDrawing) {\r\n uploadUrl += \"?drawing=true\";\r\n }\r\n\r\n if (isImageDrawingUpdate) {\r\n method = \"PUT\";\r\n\r\n attachmentFormData.append(\"filename\", metadata.filename);\r\n attachmentFormData.append(\"formId\", FORMID);\r\n }\r\n\r\n uploadXHR.open(method, uploadUrl, true);\r\n\r\n return {\r\n abort: uploadXHR.abort.bind(window),\r\n send: function () {\r\n uploadXHR.send(attachmentFormData);\r\n }\r\n };\r\n }\r\n}\r\n\r\nfunction saveFormOnNetwork(fullData, attachments, matrixAttachmentData, action, callback) {\r\n var attachmentsParam = [{ name: \"attachments\", value: JSON.stringify(attachments) }];\r\n var matrixAttachmentsParam = [{ name: \"matrix-attachments\", value: JSON.stringify(matrixAttachmentData) }];\r\n\r\n fullData = fullData.concat(attachmentsParam, matrixAttachmentsParam);\r\n var fullStringData = $.param(fullData);\r\n\r\n var isNew = FORMID === \"-1\";\r\n var url = baseUrl + \"/api/forms\" + (isNew ? \"\" : \"/\" + FORMID) + (action === \"forward\" ? \"/forward\" : \"\") + (isNew ? \"?formTypeId=\" + FORMTYPEID : \"\");\r\n\r\n $.ajax({\r\n url: url,\r\n type: isNew ? \"POST\" : \"PUT\",\r\n dataType: \"json\",\r\n data: fullStringData,\r\n error: handleFormSaveError,\r\n success: function (data) {\r\n var savedId = data.oid;\r\n FORMID = savedId;\r\n FORMGUID = data.guid;\r\n\r\n showExportButtons();\r\n toggleLoadingOverlay(false);\r\n\r\n if (action === \"forward\") {\r\n var restartParam = restartId > 0 ? \"&restart-id=\" + restartId : \"\";\r\n var confirmUrl = \"/Confirm?formId=\" + savedId + restartParam;\r\n\r\n // If we are within the application frame (logged in user) the application class exists.\r\n if (typeof application !== \"undefined\" && typeof application !== \"undefined\") {\r\n application.setIframeUrl(confirmUrl);\r\n }\r\n else {\r\n location.href = baseUrl + confirmUrl;\r\n }\r\n\r\n return;\r\n }\r\n\r\n $(\".attachment-container\").each(function () {\r\n var attachmentPreview = $(this);\r\n var state = attachmentPreview.data(\"state\");\r\n\r\n if (state === \"saved\") {\r\n return;\r\n }\r\n\r\n var questionId = attachmentPreview.data(\"question\");\r\n var guid = attachmentPreview.data(\"guid\");\r\n var link = attachmentPreview.find(\"a\");\r\n var displayName = attachmentPreview.find(\".attachment-text\").text();\r\n\r\n var fileName = FORMID + \"_\" + questionId + \"_\" + guid;\r\n var newUrl = baseUrl + \"/api/attachments?fileName=\" + encodeURIComponent(fileName) + \"&displayName=\" + encodeURIComponent(displayName);\r\n\r\n link.attr(\"href\", newUrl);\r\n attachmentPreview.data(\"filename\", fileName);\r\n attachmentPreview.data(\"state\", \"saved\");\r\n });\r\n\r\n var updatedMatrices = [];\r\n\r\n for (var matrixQuestionId in matrixAttachments) {\r\n var lines = matrixAttachments[matrixQuestionId];\r\n\r\n for (var lineId in lines) {\r\n lines[lineId].forEach(function (attachment) {\r\n if (attachment.guid) {\r\n attachment.fileName = FORMID + \"_\" + attachment.questionId + \"_\" + attachment.guid;\r\n }\r\n\r\n if (attachment.guid && attachment.state !== \"saved\" && !updatedMatrices.includes(matrixQuestionId)) {\r\n updatedMatrices.push(matrixQuestionId);\r\n }\r\n\r\n attachment.state = \"saved\";\r\n });\r\n }\r\n }\r\n\r\n // Update matrix attachment links.\r\n updatedMatrices.forEach(updatedMatrixId => {\r\n redrawMatrix(`md_${updatedMatrixId}`);\r\n });\r\n\r\n $(\"input[name^='file_']\").val(\"\");\r\n\r\n Object.keys(changedDrawings).forEach(function (drawingId) {\r\n $(\"#btn_\" + drawingId).hide();\r\n\r\n var canvas = $(\"#C_\" + drawingId).get(0).fabric;\r\n\r\n canvas.isDrawingMode = false;\r\n canvas.hoverCursor = \"default\";\r\n canvas.forEachObject(function (fabricObject) {\r\n fabricObject.set(\"selectable\", false);\r\n });\r\n\r\n $(\"#file_\" + drawingId).val(\"\");\r\n });\r\n\r\n changedDrawings = {};\r\n\r\n core.notify(\"success\", resources.saveSuccessMessage);\r\n\r\n setProgress(\"hide\");\r\n toggleSaveButtons(true);\r\n\r\n initActionsGrid({ id: savedId });\r\n\r\n var codeDisplayEl = $(\"#code-display\");\r\n var currentCode = codeDisplayEl.text();\r\n if (currentCode !== data.code) {\r\n codeDisplayEl.text(data.code);\r\n codeDisplayEl.parent().addClass(\"code-changed\");\r\n }\r\n\r\n callback && callback();\r\n }\r\n });\r\n}\r\n\r\nfunction saveFormOffline(action, fullData) {\r\n var attachmentPromises = [];\r\n var previewElements = [];\r\n var offlineAttachments = [];\r\n\r\n $(\".attachment-container\").each(function () {\r\n var file = $(this);\r\n\r\n var isDatasetValue = file.data(\"isDatasetValue\");\r\n var isCopy = file.data(\"isCopy\");\r\n var offlineId = file.data(\"offline-id\");\r\n\r\n if (isDatasetValue || isCopy) {\r\n // We will not have the file available in this case.\r\n // File is copied server-side through JSON in hidden file_ input.\r\n return;\r\n }\r\n\r\n if (typeof offlineId !== \"undefined\") {\r\n offlineAttachments.push(offlineId);\r\n return;\r\n }\r\n\r\n var questionId = file.data(\"question\");\r\n var rawFile = file.data(\"file\");\r\n var displayName = file.find(\".attachment-text\").text();\r\n\r\n var data = {\r\n type: \"file\",\r\n data: rawFile,\r\n displayName: displayName,\r\n questionId: questionId\r\n };\r\n\r\n var dbPromise = db.attachments.add(data);\r\n attachmentPromises.push(dbPromise);\r\n previewElements.push(file);\r\n });\r\n\r\n var changedDrawingElements = [];\r\n\r\n $(\".q-input .canvas-container\").each(function () {\r\n var container = $(this);\r\n var canvas = container.find(\".lower-canvas\");\r\n\r\n var offlineId = canvas.data(\"offline-id\");\r\n var hasOfflineId = typeof offlineId !== \"undefined\";\r\n var questionId = canvas.attr(\"id\").substring(2);\r\n\r\n if (changedDrawings.hasOwnProperty(questionId)) {\r\n var drawingData = changedDrawings[questionId];\r\n var data = {\r\n type: \"drawing\",\r\n data: drawingData,\r\n questionId: questionId\r\n };\r\n\r\n var dbPromise;\r\n if (hasOfflineId) {\r\n data.id = offlineId;\r\n dbPromise = indexedDatabase.attachments.put(data);\r\n }\r\n else {\r\n dbPromise = indexedDatabase.attachments.add(data);\r\n }\r\n\r\n attachmentPromises.push(dbPromise);\r\n changedDrawingElements.push(canvas);\r\n }\r\n else if (hasOfflineId) {\r\n offlineAttachments.push(offlineId);\r\n }\r\n });\r\n\r\n var fullMatrixAttachmentData = getMatrixAttachments(true);\r\n var matrixAttachmentData = fullMatrixAttachmentData.attachmentData;\r\n\r\n var flatMatrixAttachments = matrixAttachmentData\r\n .reduce(function (accu, matrix) {\r\n var lines = matrix.lines.reduce(function (accu, line) {\r\n return accu.concat(line.attachments);\r\n }, []);\r\n\r\n return accu.concat(lines);\r\n }, [])\r\n .filter(function (attachment) {\r\n var hasOfflineId = typeof attachment.offlineId !== \"undefined\";\r\n if (hasOfflineId && (attachment.type !== \"drawing\" || !attachment.data)) {\r\n offlineAttachments.push(attachment.offlineId);\r\n return false;\r\n }\r\n\r\n return true;\r\n });\r\n\r\n flatMatrixAttachments.forEach(function (attachment) {\r\n var hasOfflineId = typeof attachment.offlineId !== \"undefined\";\r\n\r\n if (attachment.type === \"drawing\" && hasOfflineId) {\r\n attachment.id = +attachment.offlineId;\r\n delete attachment.offlineId;\r\n\r\n var updatePromise = indexedDatabase.attachments.put(attachment);\r\n attachmentPromises.push(updatePromise);\r\n\r\n return;\r\n }\r\n\r\n delete attachment.offlineId;\r\n\r\n var dbPromise = indexedDatabase.attachments.add(attachment);\r\n attachmentPromises.push(dbPromise);\r\n });\r\n\r\n Promise.all(attachmentPromises)\r\n .then(function (attachmentIds) {\r\n var filesAmount = previewElements.length;\r\n var drawingsAmount = changedDrawingElements.length;\r\n var drawingsIndex = filesAmount + drawingsAmount;\r\n\r\n var fileIds = attachmentIds.slice(0, filesAmount);\r\n var drawingIds = attachmentIds.slice(filesAmount, drawingsIndex);\r\n var matrixIds = attachmentIds.slice(drawingsIndex);\r\n\r\n fileIds.forEach(function (id, index) {\r\n previewElements[index].data(\"offline-id\", id);\r\n });\r\n\r\n drawingIds.forEach(function (id, index) {\r\n changedDrawingElements[index].data(\"offline-id\", id);\r\n });\r\n\r\n matrixIds.forEach(function (id, index) {\r\n var attachment = flatMatrixAttachments[index];\r\n\r\n attachment.offlineId = id;\r\n delete attachment.data;\r\n delete attachment.metadata;\r\n });\r\n\r\n var formTitle = $(\".formtitle\").first().text();\r\n var offlineData = {\r\n userId: USERID,\r\n action: action,\r\n formTypeId: FORMTYPEID,\r\n baseId: BASEID,\r\n data: fullData,\r\n attachments: offlineAttachments.concat(attachmentIds),\r\n matrixAttachments: matrixAttachmentData,\r\n createdAt: Date.now(),\r\n title: formTitle\r\n };\r\n\r\n return indexedDatabase.transaction(\"rw\", indexedDatabase.forms, function () {\r\n if (OFFLINEID !== -1) {\r\n indexedDatabase.forms.update(OFFLINEID, offlineData);\r\n }\r\n else {\r\n return indexedDatabase.forms.add(offlineData);\r\n }\r\n });\r\n })\r\n .then(function (returnValue) {\r\n if (window.navigator.onLine) {\r\n parent.syncOfflineData();\r\n }\r\n\r\n toggleLoadingOverlay(false);\r\n\r\n if (action === \"forward\" && typeof application !== \"undefined\") {\r\n var url = \"/Confirm?offline=true&restart-id=\" + BASEID;\r\n application.setIframeUrl(url);\r\n }\r\n else {\r\n if (OFFLINEID === -1) {\r\n OFFLINEID = returnValue;\r\n }\r\n\r\n $(\".attachment-container\").data(\"offline-saved\", true);\r\n\r\n changedDrawings = {};\r\n core.notify(\"info\", resources.savedOfflineMessage);\r\n toggleSaveButtons(true);\r\n }\r\n })\r\n .catch(function (err) {\r\n handlePotentialQuotaError(err);\r\n handleFormSaveError(err);\r\n });\r\n}\r\n\r\nfunction handlePotentialQuotaError(err) {\r\n if (err.name === \"QuotaExceededError\" || (err.inner && err.inner.name === \"QuotaExceededError\")) {\r\n core.notify(\"error\", resources.quotaErrorMessage);\r\n return true;\r\n }\r\n}\r\n\r\nfunction resetFormSaveUI() {\r\n toggleSaveButtons(true);\r\n toggleLoadingOverlay(false);\r\n setProgress(\"hide\");\r\n}\r\n\r\nfunction handleFormSaveError() {\r\n resetFormSaveUI();\r\n leanformsNext.handleError(arguments);\r\n}\r\n\r\nfunction toggleSaveButtons(state) {\r\n $(\".formtype-toolbar .btn, #forwardFormFooter\").prop(\"disabled\", !state);\r\n}\r\n\r\nfunction saveForm(options) {\r\n options = options || {};\r\n\r\n // Disable buttons to prevent multiple clicks.\r\n toggleSaveButtons(false);\r\n\r\n if (options.forward) {\r\n // Offset is a position correction for the fixed form header (and some arbitrary extra).\r\n if (validation.validateForm({ container: document, scrollOffset: 150, customValidation: checkRequired })) {\r\n\r\n // Check for valid username.\r\n checkDatasetUsername((datasetUsernameValid) => {\r\n if (!datasetUsernameValid) {\r\n toggleSaveButtons(true);\r\n return;\r\n }\r\n\r\n sendForm(\"forward\");\r\n });\r\n }\r\n else {\r\n // Not valid, enable buttons again.\r\n toggleSaveButtons(true);\r\n }\r\n }\r\n else {\r\n sendForm(\"save\", options.callback);\r\n }\r\n}\r\n\r\nfunction exportForm() {\r\n toggleLoadingOverlay(true);\r\n core.downloadFile(`${baseUrl}/api/forms/export/${FORMID}`, $(\"form\").serialize(), () => toggleLoadingOverlay(false), false);\r\n}\r\n\r\nfunction defaultFormExport() {\r\n toggleLoadingOverlay(true);\r\n core.downloadFile(`${baseUrl}/api/forms/${FORMID}/default-form-export`, $(\"form\").serialize(), () => toggleLoadingOverlay(false), false);\r\n}\r\n\r\nfunction setProgress(percent) {\r\n if (percent === \"hide\") {\r\n document.body.style.setProperty(\"--form-progress-height\", \"0px\");\r\n\r\n setTimeout(function () {\r\n document.body.style.setProperty(\"--form-progress-width\", \"0%\");\r\n }, 250);\r\n }\r\n\r\n if (percent === 0) {\r\n document.body.style.setProperty(\"--form-progress-height\", \"5px\");\r\n }\r\n\r\n document.body.style.setProperty(\"--form-progress-width\", percent + \"%\");\r\n}\r\n\r\n/*\r\n * Actions\r\n */\r\nvar rowNumber = 0;\r\nfunction resetRowNumber() {\r\n rowNumber = 0;\r\n}\r\n\r\nfunction renderNumber() {\r\n return rowNumber++;\r\n}\r\n\r\nfunction loadActions(options) {\r\n var id = options.id;\r\n var offlineData = options.offlineData;\r\n\r\n var config = {\r\n schema: {\r\n model: {\r\n id: \"oid\",\r\n fields: {\r\n closedByName: { type: \"string\" },\r\n description: { type: \"string\" },\r\n initiatorId: { type: \"number\" },\r\n initiatorName: { type: \"string\" },\r\n oid: { type: \"number\" },\r\n parentQuestionId: { type: \"number\" },\r\n planDate: { type: \"datetime\" },\r\n questionBase: { type: \"string\" },\r\n questionId: { type: \"number\" },\r\n questionNumber: { type: \"string\" },\r\n remarks: { type: \"string\" },\r\n selectedType: { type: \"number\" },\r\n statusHtml: { type: \"string\" },\r\n userId: { type: \"number\" },\r\n username: { type: \"string\" },\r\n informGroupAllowUser: { type: \"boolean\" },\r\n informGroup: { type: \"number\" }\r\n }\r\n }\r\n }\r\n };\r\n\r\n if (id) {\r\n config.transport = {\r\n read: {\r\n dataType: \"json\",\r\n url: baseUrl + \"/api/actions/form/\" + id\r\n }\r\n }\r\n }\r\n\r\n var dataSource = new kendo.data.DataSource(config);\r\n\r\n if (offlineData) {\r\n // Setting the data directly causes unwanted properties to be added to the object.\r\n var data = JSON.parse(offlineData);\r\n\r\n data.forEach(function (action) {\r\n dataSource.add(action);\r\n });\r\n }\r\n\r\n return dataSource;\r\n}\r\n\r\nfunction initActionsGrid(options) {\r\n var id = options ? options.id : null;\r\n var grid = $(\"#grdActions\").data(\"kendoGrid\");\r\n\r\n if (grid) {\r\n if (id) {\r\n grid.setDataSource(loadActions(options));\r\n grid.refresh();\r\n }\r\n\r\n return;\r\n }\r\n\r\n $(\"#actions-container\").html('
    ');\r\n\r\n var refreshActions = true;\r\n\r\n $(\"#grdActions\").kendoGrid({\r\n dataSource: options ? loadActions(options) : [],\r\n columns: [\r\n { field: \"statusHtml\", title: resources.actionGridTexts.state, width: 58, encoded: false },\r\n {\r\n field: \"questionNumber\",\r\n title: resources.actionGridTexts.number,\r\n width: 100,\r\n template: (dataItem) => {\r\n return ``;\r\n },\r\n },\r\n { field: \"description\", title: resources.actionGridTexts.description },\r\n {\r\n field: \"planDate\",\r\n title: resources.actionGridTexts.planDate,\r\n width: 93,\r\n template: (dataItem) => {\r\n return dating.formatDate(dataItem.planDate, formats.dateDisplayFormat);\r\n },\r\n },\r\n { field: \"initiatorName\", title: resources.actionGridTexts.initiator, width: 150 },\r\n { field: \"username\", title: resources.actionGridTexts.user, width: 150 },\r\n { field: \"remarks\", title: resources.actionGridTexts.remarks },\r\n {\r\n field: \"closedDate\",\r\n title: resources.actionGridTexts.closedDate,\r\n width: 108,\r\n template: (dataItem) => {\r\n return dating.formatDate(dataItem.closedDate, formats.dateDisplayFormat);\r\n },\r\n },\r\n { field: \"closedByName\", title: resources.actionGridTexts.closedBy, width: 150 },\r\n { field: \"oid\", hidden: true }\r\n ],\r\n scrollable: false,\r\n dataBound: function () {\r\n resetRowNumber();\r\n\r\n const firstCells = document.querySelector(\"#grdActions\")?.querySelectorAll(\"tbody > tr > td:first-child\");\r\n [...firstCells].forEach((cell) => {\r\n $(cell).kendoTooltip({\r\n position: \"top\",\r\n content: (e) => {\r\n return e.target.text();\r\n }\r\n })\r\n });\r\n\r\n if (refreshActions) {\r\n refreshActions = false;\r\n refreshActionGrid();\r\n }\r\n\r\n $(\"#actions-container\").toggleClass(\"hidden\", !firstCells.length);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Add a form action to the actions grid at the bottom of the page, the 'saveForm' method actually saves any then existing actions.\r\n * @param {FormData} formData The form (fields) data.\r\n * @param {any} usersInGroup The selected action owner (can be a user, group or an array of users).\r\n * @param {Integer} parentQuestionId The referenced parent question.\r\n */\r\nfunction saveAction(formData, usersInGroup, parentQuestionId) {\r\n let actionGrid = null;\r\n\r\n if (!isMatrix) {\r\n initActionsGrid();\r\n actionGrid = $(\"#grdActions\").data(\"kendoGrid\");\r\n }\r\n\r\n const isClosed = !!formData.get(\"closedAction\");\r\n const informGroup = +formData.get(\"informGroup\");\r\n const closedDate = formData.get(\"closedDate\");\r\n const description = formData.get(\"description\");\r\n const planDate = formData.get(\"planDate\");\r\n const questionId = +formData.get(\"questionId\");\r\n const questionNumber = formData.get(\"questionNumber\");\r\n const remarks = formData.get(\"remarks\");\r\n const selectedType = +formData.get(\"actionType\");\r\n\r\n // We save the action per user.\r\n usersInGroup.forEach(async (selectedUser) => {\r\n if (selectedAction && !isMatrix) {\r\n // Modify existing action.\r\n selectedAction.description = description;\r\n selectedAction.planDate = dating.parseDateString(planDate, formats.dateDisplayFormat);\r\n selectedAction.informGroup = informGroup;\r\n\r\n // When the action is generated from 'User in group', we store the type as 'user'.\r\n selectedAction.selectedType = selectedType === 3 ? 1 : selectedType;\r\n selectedAction.userId = +selectedUser.userId;\r\n selectedAction.username = selectedUser.username;\r\n selectedAction.remarks = remarks;\r\n\r\n // Can be done, notsaved, overdue or planned.\r\n // overdue and planned are not set correctly after updating action.\r\n let stateClass = \"notsaved\";\r\n\r\n if (isClosed) {\r\n selectedAction.closedById = USERID;\r\n selectedAction.closedByName = USERNAME;\r\n selectedAction.closedDate = dating.parseDateString(closedDate, formats.dateDisplayFormat);\r\n\r\n stateClass = \"done\";\r\n }\r\n else if (selectedAction.oid === -1) {\r\n selectedAction.closedById = null;\r\n selectedAction.closedByName = null;\r\n selectedAction.closedDate = null;\r\n }\r\n\r\n let stateText = resources.actionGridTexts[\"state\" + stateClass];\r\n\r\n if (stateClass !== \"notsaved\") {\r\n // Double parse to prevent XSS.\r\n stateText += \" \" + dating.formatDate(dating.parseDateString(closedDate, formats.dateDisplayFormat), formats.dateDisplayFormat);\r\n }\r\n\r\n selectedAction.statusHtml = `${stateText}`;\r\n\r\n actionGrid.refresh();\r\n\r\n if (selectedAction.oid !== -1) {\r\n var response = await core.fetch({\r\n url: baseUrl + \"/api/actions/\" + selectedAction.oid,\r\n config: {\r\n body: formData,\r\n method: \"PUT\"\r\n }\r\n }).catch(leanformsNext.onAjaxError);\r\n \r\n if (!response?.ok) {\r\n leanformsNext.onAjaxError(response);\r\n }\r\n }\r\n }\r\n else {\r\n // New action.\r\n var action = {\r\n description: description,\r\n initiatorId: USERID,\r\n initiatorName: USERNAME,\r\n oid: -1,\r\n parentQuestionId: parentQuestionId,\r\n planDate: dating.parseDateString(planDate, formats.dateDisplayFormat),\r\n questionId: questionId,\r\n questionNumber: questionNumber,\r\n selectedType: selectedType === 3 ? 1 : selectedType,\r\n statusHtml: `${resources.actionGridTexts.statenotsaved}`,\r\n userId: +selectedUser.userId,\r\n username: selectedUser.username,\r\n informGroup: informGroup\r\n };\r\n\r\n if (isMatrix) {\r\n parent.matrixActions.push(action);\r\n }\r\n else {\r\n actionGrid.dataSource.add(action);\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Called by editAction and by the event on the form (action) button.\r\n * @param {Integer} questionId\r\n * @param {String} questionNumber\r\n * @param {Integer} parentQuestionId\r\n * @param {Integer} groupId\r\n */\r\nfunction showAction(questionId, questionNumber, parentQuestionId, groupId, informGroupAllowUser, informGroup) {\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n $(\"#windowcontainer\").append(\"
    \");\r\n $(\"#dialog\").kendoWindow({\r\n width: leanformsNext.getPopupWidth(1025),\r\n height: leanformsNext.getPopupHeight(710),\r\n title: resources.viewForm.action,\r\n visible: false,\r\n modal: true,\r\n iframe: true,\r\n deactivate: function () {\r\n this.destroy();\r\n },\r\n content: `${baseUrl}/Dialogs/ActionDialog?isFormAction=true&id=${selectedAction?.oid ?? -1}&isNewAction=${selectedAction === null}&questionId=${questionId}&questionNumber=${questionNumber}&parentQuestionId=${parentQuestionId}&groupId=${groupId}&informGroupAllowUser=${informGroupAllowUser}&informGroup=${informGroup}`,\r\n close: function () {\r\n $(\"body\").css(\"overflow\", \"\");\r\n // New selected action is set when a row in the actions grid is clicked.\r\n selectedAction = null;\r\n }\r\n }).data(\"kendoWindow\").center().open();\r\n}\r\n\r\nfunction toggleActionButtons(state) {\r\n $(\"#action-popup .dialog-toolbar .btn\").prop(\"disabled\", !state);\r\n}\r\n\r\nfunction closeActionDialog() {\r\n toggleActionButtons(false);\r\n $(\"#action-popup\").data(\"kendoWindow\").close();\r\n}\r\n\r\nfunction editAction(index) {\r\n var grd = $(\"#grdActions\").data(\"kendoGrid\");\r\n var selected = grd._data[index];\r\n selectedAction = selected;\r\n\r\n showAction(selected.questionId, selected.questionNumber, selected.parentQuestionId, null, selected.informGroupAllowUser, selected.informGroup);\r\n}\r\n\r\nfunction deleteAction() {\r\n $(\"#grdActions\").data(\"kendoGrid\").dataSource.remove(selectedAction);\r\n $(\"#grdActions\").data(\"kendoGrid\").refresh();\r\n}\r\n\r\nfunction getMatrixLineNumber(action) {\r\n var badge = \"\";\r\n\r\n if (!action.questionBase) {\r\n action.questionBase = action.questionNumber;\r\n }\r\n\r\n if (action.parentQuestionId) {\r\n var matrix = $(\"#id_\" + action.parentQuestionId);\r\n var parentQuestionNumber = $('div[q=\"' + action.parentQuestionId + '\"] .q-number').text();\r\n var lineNumber = matrix.find('tr[data-lineid=\"' + action.lineId + '\"] > td').eq(0).text();\r\n var badgeTitle = resources.viewForm.badgeTitle.replace(\"{0}\", parentQuestionNumber).replace(\"{1}\", lineNumber);\r\n var lineText = lineNumber > 0 ? ' ' + resources.viewForm.matrixLineNumber + lineNumber : '';\r\n\r\n badge = '' + parentQuestionNumber + lineText + ' ';\r\n }\r\n\r\n return badge + (action.questionBase ?? \"\");\r\n}\r\n\r\nfunction refreshActionGrid() {\r\n var actionsGrid = $(\"#grdActions\").data(\"kendoGrid\");\r\n if (!actionsGrid) {\r\n return;\r\n }\r\n\r\n actionsGrid.refresh();\r\n}\r\n\r\nfunction getMatrixLineActions(questionId, lineId) {\r\n var actionsGrid = $(\"#grdActions\").data(\"kendoGrid\");\r\n if (!actionsGrid) {\r\n return;\r\n }\r\n\r\n return actionsGrid._data.filter(action => action.parentQuestionId == questionId && action.lineId == lineId);\r\n}\r\n\r\n/*\r\n * Offline availability\r\n */\r\nfunction toggleOfflineLabel() {\r\n var button = $(\"#btnToggleOffline\");\r\n button.find(\".k-icon\").toggleClass(\"k-i-globe k-i-globe-outline\");\r\n\r\n var label = button.find(\".offline-label\");\r\n var currentText = label.text();\r\n var newText = label.data(\"toggle-label\");\r\n\r\n label.text(newText);\r\n label.data(\"toggle-label\", currentText);\r\n}\r\n\r\nfunction toggleOffline() {\r\n var isAdd = $(this).find(\".k-icon\").hasClass(\"k-i-globe-outline\");\r\n\r\n if (isAdd) {\r\n installOffline();\r\n\r\n tryPersistIndexedDB();\r\n }\r\n else {\r\n // TODO: Hide while offline?\r\n removeOffline();\r\n }\r\n}\r\n\r\nfunction installOffline() {\r\n var cacheUrl = baseUrl + \"/ViewForm?handler=OfflineForm&FormTypeId=\" + BASEID;\r\n\r\n return core.fetch({ url: cacheUrl })\r\n .catch(leanformsNext.markNetworkError)\r\n .then(function (response) {\r\n return response.json();\r\n })\r\n .then(function (content) {\r\n // Previous promise step parses the response to an actual JSON object and provides it to the this step.\r\n return indexedDatabase.transaction(\"rw\", indexedDatabase.formTypeUsers, indexedDatabase.formTypes, function () {\r\n indexedDatabase.formTypeUsers.add({\r\n userId: USERID,\r\n formTypeBaseId: content.baseId\r\n });\r\n\r\n indexedDatabase.formTypes.put({\r\n baseId: content.baseId,\r\n id: content.id,\r\n lastChange: content.lastChange,\r\n lists: content.lists,\r\n html: content.html,\r\n init: content.init,\r\n toolbar: content.toolbar,\r\n matrices: content.matrices\r\n });\r\n });\r\n })\r\n .then(function () {\r\n toggleOfflineLabel();\r\n\r\n // TODO: wait for success before notifying?\r\n core.notify(\"success\", resources.typeSavedOfflineMessage);\r\n parent.updateOfflineMenu();\r\n })\r\n .catch(function (err) {\r\n if (err.message === \"lf-failed-fetch\") {\r\n return core.notify(\"warn\", resources.noNetworkMessage);\r\n }\r\n\r\n leanformsNext.handleError(err);\r\n });\r\n}\r\n\r\nfunction removeOffline() {\r\n indexedDatabase.transaction(\"rw\", indexedDatabase.formTypeUsers, indexedDatabase.formTypes, function () {\r\n indexedDatabase.formTypeUsers.where({ userId: USERID, formTypeBaseId: +BASEID }).delete();\r\n indexedDatabase.formTypes.delete(+BASEID);\r\n })\r\n .then(function () {\r\n toggleOfflineLabel();\r\n core.notify(\"info\", resources.typeRemovedOfflineMessage);\r\n parent.updateOfflineMenu();\r\n }).catch(leanformsNext.handleError);\r\n}\r\n\r\nfunction tryPersistIndexedDB() {\r\n if (window.navigator.storage && window.navigator.storage.persist) {\r\n // May prompt user.\r\n // Chrome decides itself based on heuristics, may not ask user.\r\n window.navigator.storage.persist();\r\n }\r\n}\r\n\r\n/*\r\n * Handle change\r\n */\r\nfunction handleOnChange(vanillaElement) {\r\n try {\r\n checkMessage(vanillaElement);\r\n\r\n // Remove id_, radio button id, checkbox id.\r\n var vanillaElementId = vanillaElement.id\r\n .substr(3)\r\n .split(\"_\")[0]\r\n .split(\"#\")[0];\r\n\r\n formControls.fillDependantList(vanillaElement, vanillaElementId);\r\n calc(vanillaElementId);\r\n\r\n // Do not check rights on first call (globalInit).\r\n if (!globalInit) {\r\n checkRights(vanillaElement);\r\n }\r\n\r\n changeDependentDate(vanillaElement);\r\n\r\n // Validate.\r\n vanillaElement.dispatchEvent(new Event(\"validateonchange\"));\r\n\r\n // On first call (globalInit) validate min/max (eventhandlers are not attached yet and only to be called on actual change - later).\r\n if (globalInit && vanillaElement.getAttribute(\"format\") === \"number\") {\r\n validation.validateRangeState({ element: vanillaElement });\r\n }\r\n }\r\n catch (err) {\r\n console.error(vanillaElement, err);\r\n core.notify(\"error\");\r\n }\r\n}\r\n\r\nfunction dateHandleOnChange() {\r\n const vanillaElement = this.element.get(0);\r\n handleOnChange(vanillaElement);\r\n}\r\n\r\n/*\r\n * Messages\r\n */\r\n\r\nvar dialogCounter = 0;\r\n\r\nfunction closeMessageDialog(id, dialogCounter) {\r\n var question = $(\"*[bq=\\\"\" + messages[id].copyToQuestionBase + \"\\\"]\");\r\n var value = question.val();\r\n var isFirstMessage = !value;\r\n var rawMessage = messages[id].message;\r\n var safeMessageHtml = core.sanitizeHtml(core.xmlDecode(rawMessage));\r\n\r\n if (isFirstMessage) {\r\n var parent = question.parent();\r\n\r\n if (parent.hasClass(\"grow-wrap\")) {\r\n // Fixes layout issue in multi-line text inputs.\r\n parent.removeClass(\"grow-wrap\");\r\n }\r\n\r\n question.val(rawMessage);\r\n question.hide();\r\n question.after(\"
    \" + safeMessageHtml + \"
    \");\r\n }\r\n else {\r\n question.val(value + core.xmlEncode(\"
    \") + rawMessage);\r\n\r\n var display = question.next(\".msg\");\r\n display.append(\"
    \" + safeMessageHtml);\r\n }\r\n\r\n $(\".msgdialog\").eq(dialogCounter).data(\"kendoWindow\").close();\r\n}\r\n\r\nfunction checkMessage(obj, init) {\r\n try {\r\n var initialized = [];\r\n\r\n for (var i = 0; i < messages.length; i++) {\r\n if (init) {\r\n var copyToQuestion = messages[i].copyToQuestionBase;\r\n\r\n if (copyToQuestion === +$(obj).attr(\"bq\") && $(\"*[bq=\\\"\" + copyToQuestion + \"\\\"]\").val() && initialized.indexOf(copyToQuestion) === -1) {\r\n var questionEl = $(\"*[bq=\\\"\" + copyToQuestion + \"\\\"]\");\r\n var parent = questionEl.parent();\r\n\r\n if (parent.hasClass(\"grow-wrap\")) {\r\n // Fixes layout issue in multi-line text inputs.\r\n parent.removeClass(\"grow-wrap\");\r\n }\r\n\r\n var msg = questionEl.val();\r\n if (msg.indexOf(\"<\") > -1) {\r\n questionEl.val(core.xmlEncode(msg));\r\n }\r\n\r\n var safeMessageHtml = core.sanitizeHtml(core.xmlDecode(msg));\r\n questionEl.hide();\r\n questionEl.after(\"
    \" + safeMessageHtml + \"
    \");\r\n initialized.push(copyToQuestion);\r\n }\r\n } else if (messages[i].questionBase === +$(obj).attr(\"bq\")\r\n && validation.validateValueCompare({ leftValue: $(obj).val(), operator: messages[i].operator, rightValue: messages[i].value })) {\r\n\r\n var win = $(\"
    \");\r\n $(\"#windowcontainer\").append(win);\r\n\r\n var msgWindow = $(\"#msgdialog\").kendoWindow({\r\n visible: false,\r\n height: leanformsNext.getPopupHeight(700),\r\n width: leanformsNext.getPopupWidth(900),\r\n title: i,\r\n modal: true,\r\n iframe: true,\r\n deactivate: function () {\r\n this.destroy();\r\n\r\n // No more message dialogs.\r\n if ($(\".msgdialog\").length === 0) {\r\n $(\"body\").css(\"overflow\", \"\");\r\n dialogCounter = 0;\r\n }\r\n },\r\n actions: {},\r\n refresh: function () {\r\n var msgid = parseInt(this.title());\r\n var msgno = messages.length - msgid;\r\n var rawMessage = messages[msgid].message;\r\n var safeMessage = core.sanitizeHtml(core.xmlDecode(rawMessage));\r\n var messageContainer = this.element.children(\".k-content-frame\").contents().find(\".lf-message\");\r\n var title = resources.messageTitle + \" \" + msgno + \"/\" + messages.length;\r\n\r\n this.title(title);\r\n messageContainer.html(safeMessage);\r\n },\r\n }).data(\"kendoWindow\");\r\n\r\n msgWindow.refresh({\r\n url: baseUrl + \"/Dialogs/MessageDialogFrame?id=\" + i + \"&idx=\" + dialogCounter\r\n });\r\n\r\n // Dialog opened.\r\n dialogCounter++;\r\n\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n msgWindow.center().open();\r\n }\r\n }\r\n }\r\n catch (err) {\r\n console.error(err);\r\n }\r\n}\r\n\r\n/*\r\n * Date calculation\r\n */\r\n\r\nfunction setDateValue(el, _date, amount, amountfield, unit) {\r\n if (parseInt(amountfield, 10) !== -1) {\r\n var fld = $(\"[bq=\\\"\" + amountfield + \"\\\"]\").get(0);\r\n\r\n if (fld.tagName === \"INPUT\" && fld.type === \"radio\") {\r\n fld = $(\"[bq=\\\"\" + amountfield + \"\\\"]:checked\").get(0);\r\n }\r\n\r\n if (!fld) {\r\n return false;\r\n }\r\n\r\n var fieldAmount = null;\r\n if (fld.tagName === \"INPUT\" && fld.type === \"text\") {\r\n fieldAmount = $(fld).val();\r\n }\r\n\r\n if (fld.tagName === \"INPUT\" && fld.type === \"radio\") {\r\n if (fld.getAttribute(\"alt\") != null && fld.getAttribute(\"alt\") != \"\") {\r\n fieldAmount = fld.getAttribute(\"alt\");\r\n }\r\n else if (fld.getAttribute(\"text\") != null && fld.getAttribute(\"text\") != \"\") {\r\n fieldAmount = fld.getAttribute(\"text\");\r\n }\r\n else {\r\n fieldAmount = fld.text;\r\n }\r\n }\r\n\r\n if (fld.tagName === \"SELECT\") {\r\n var selectedOption = $(fld).children().eq(fld.selectedIndex);\r\n if (selectedOption.length) {\r\n if (selectedOption.data(\"alt\") != \"\") {\r\n fieldAmount = selectedOption.data(\"alt\");\r\n } else {\r\n fieldAmount = selectedOption.text();\r\n }\r\n }\r\n }\r\n\r\n amount = parseInt(fieldAmount);\r\n }\r\n\r\n if (isNaN(amount)) {\r\n return false;\r\n }\r\n\r\n switch (unit) {\r\n case \"1\": { _date.addMinutes(amount); break; }\r\n case \"2\": { _date.addHours(amount); break; }\r\n case \"3\": { _date.addDays(amount); break; }\r\n case \"4\": { _date.addMonths(amount); break; }\r\n case \"5\": { _date.addYears(amount); break; }\r\n }\r\n\r\n var dateWidgetType = datePickerMap[$(el).attr(\"data-role\")];\r\n if (dateWidgetType) {\r\n $(el).data(dateWidgetType).value(_date);\r\n }\r\n else {\r\n var displayDate = _date.getDate() + \"-\" + (_date.getMonth() + 1) + \"-\" + _date.getFullYear();\r\n var displayTime = pad(_date.getHours(), 2) + \":\" + pad(_date.getMinutes(), 2);\r\n var dateType = $(el).attr(\"datetype\");\r\n\r\n if (dateType === \"0\") {\r\n $(el).val(displayDate);\r\n }\r\n else if (dateType === \"1\") {\r\n $(el).val(displayTime);\r\n }\r\n else if (dateType === \"2\") {\r\n $(el).val(displayDate + \" \" + displayTime);\r\n }\r\n }\r\n}\r\n\r\nfunction pad(num, size) {\r\n var s = num + \"\";\r\n while (s.length < size) {\r\n s = \"0\" + s;\r\n }\r\n\r\n return s;\r\n}\r\n\r\nfunction changeDependentDate(triggerEl) {\r\n var baseId = triggerEl.getAttribute(\"bq\");\r\n\r\n if (dateTriggers.indexOf(baseId) === -1) {\r\n return;\r\n }\r\n\r\n var isAmountField = false;\r\n var dateFields = $(\"input[field=\\\"\" + baseId + \"\\\"]\");\r\n\r\n if (dateFields.length === 0) {\r\n dateFields = $(\"[amountfield=\\\"\" + baseId + \"\\\"]\");\r\n isAmountField = true;\r\n }\r\n\r\n var $sourceEl = isAmountField ? $() : $(triggerEl);\r\n\r\n for (var i = 0; i < dateFields.length; i++) {\r\n var targetField = dateFields[i];\r\n var $targetField = $(targetField);\r\n var sourceBaseId = $targetField.attr(\"field\");\r\n\r\n if (isAmountField) {\r\n $sourceEl = $(\"[bq=\\\"\" + sourceBaseId + \"\\\"]\");\r\n }\r\n\r\n var sourceType = datePickerMap[$sourceEl.attr(\"data-role\")];\r\n var sourceDate;\r\n\r\n if (!sourceBaseId || sourceBaseId === \"-1\") {\r\n // Source is current date.\r\n sourceDate = new Date();\r\n }\r\n else if (sourceType) {\r\n // Source is date picker.\r\n var datePickerDate = $sourceEl.data(sourceType).value();\r\n\r\n // Don't calculate with invalid date in source date picker.\r\n if (datePickerDate) {\r\n sourceDate = datePickerDate;\r\n }\r\n }\r\n else {\r\n // Source is readonly field or datepicker that is not initialised.\r\n var sourceValue = $sourceEl.val();\r\n\r\n if (dating.isDateStr(sourceValue) === \"\") {\r\n sourceDate = dating.getDateFromString(sourceValue);\r\n }\r\n }\r\n\r\n if (sourceDate) {\r\n var amount = parseInt($targetField.attr(\"amount\"));\r\n var amountfield = $targetField.attr(\"amountfield\");\r\n var unit = $targetField.attr(\"unit\");\r\n\r\n setDateValue(targetField, new Date(sourceDate), amount, amountfield, unit);\r\n }\r\n }\r\n}\r\n\r\n/*\r\n * Formula\r\n */\r\nvar vCacheEls = {};\r\n\r\nfunction vCache(id) {\r\n var cached = vCacheEls[id];\r\n\r\n if (!cached) {\r\n cached = document.getElementById(id);\r\n\r\n if (!cached) {\r\n // Radio button.\r\n var options = document.querySelectorAll(`input[id^=\"${id}_\"]`);\r\n \r\n if (!options.length) {\r\n // Checkbox.\r\n options = document.querySelectorAll(`input[id^=\"${id}#\"]`);\r\n }\r\n\r\n if (options.length) {\r\n cached = Array.from(options);\r\n } else {\r\n // Element does not exist or has no options.\r\n cached = \"no-cache\";\r\n }\r\n }\r\n\r\n // Add to cache.\r\n vCacheEls[id] = cached;\r\n }\r\n\r\n return cached;\r\n}\r\n\r\nfunction getFormulaValue(str, customDefaultValue) {\r\n // Used by formulas to get question value.\r\n var hasCustomDefaultValue = typeof customDefaultValue === \"number\";\r\n var defaultValue = hasCustomDefaultValue ? customDefaultValue : \"\";\r\n var alternateDefaultValue = hasCustomDefaultValue ? customDefaultValue : undefined;\r\n\r\n try {\r\n if (str === \"id_-1\") {\r\n return defaultValue;\r\n }\r\n\r\n var val = '';\r\n var el = vCache(str);\r\n\r\n if (el === \"no-cache\") {\r\n return alternateDefaultValue;\r\n }\r\n\r\n if (Array.isArray(el)) {\r\n // Radio button or checkbox.\r\n var type = el[0].type;\r\n\r\n if (type === \"radio\") {\r\n var selectedRadio = el.find(function (radio) {\r\n return radio.checked;\r\n });\r\n\r\n if (!selectedRadio) {\r\n // No option selected.\r\n return alternateDefaultValue;\r\n }\r\n\r\n val = selectedRadio.getAttribute(\"alt\");\r\n } else if (type === \"checkbox\") {\r\n var checkedCheckboxes = el.filter(function (checkbox) {\r\n return checkbox.checked;\r\n });\r\n\r\n if (!checkedCheckboxes.length) {\r\n // Nothing is checked.\r\n return alternateDefaultValue;\r\n }\r\n\r\n // Sum values.\r\n val = checkedCheckboxes\r\n .map(function (checkbox) {\r\n return parseFloat(checkbox.getAttribute(\"alt\")) || 0;\r\n })\r\n .reduce(function (total, value) {\r\n return total + value;\r\n }, 0)\r\n .toString();\r\n } else {\r\n return alternateDefaultValue;\r\n }\r\n } else if ((el.tagName === \"INPUT\" && el.type === \"text\") || el.tagName === \"TEXTAREA\") {\r\n val = el.value;\r\n } else if (el.tagName === \"SELECT\") {\r\n var selectedOption = $(el).children().eq(el.selectedIndex);\r\n\r\n if (selectedOption.length) {\r\n var optionAlt = selectedOption.data(\"alt\");\r\n if (typeof optionAlt !== \"undefined\" && optionAlt !== \"\") {\r\n // jQuery casts to number if possible, val has to be string for isDateStr().\r\n val = optionAlt + \"\";\r\n }\r\n else {\r\n val = selectedOption.text();\r\n }\r\n }\r\n }\r\n\r\n if (dating.isDateStr(val) === \"\") {\r\n return dating.getDateFromString(val);\r\n }\r\n else {\r\n var numericVal = parseFloat(val);\r\n\r\n if (isNaN(numericVal)) {\r\n return defaultValue;\r\n }\r\n else {\r\n return numericVal;\r\n }\r\n }\r\n }\r\n catch (ex) {\r\n console.error(str, ex);\r\n }\r\n}\r\n\r\nvar formulaCache = {};\r\n\r\nfunction calc(triggerId) {\r\n var triggeredFormulas = formulaTriggers[triggerId];\r\n\r\n if (!triggeredFormulas) {\r\n return;\r\n }\r\n\r\n triggeredFormulas.forEach(function callCalculate(formulaId) {\r\n var f = formulaCache[formulaId];\r\n calculateFormula(f);\r\n });\r\n}\r\n\r\nfunction calculateFormula(f) {\r\n var result = 0;\r\n\r\n try {\r\n result = +eval(f.formula) || 0;\r\n }\r\n catch (ex) {\r\n return handleFormulaError(f.id, ex);\r\n }\r\n\r\n var decimals = f.decimals;\r\n var currentVal = f.element.val();\r\n\r\n if (decimals < 0 || decimals > 100) {\r\n decimals = 0;\r\n }\r\n\r\n if (result.toFixed(decimals) !== currentVal) {\r\n f.element.val(result.toFixed(decimals));\r\n\r\n var vanillaElement = f.element.get(0);\r\n handleOnChange(vanillaElement);\r\n }\r\n}\r\n\r\nfunction initFormulaCache() {\r\n $(\"input.formula\").each(function () {\r\n var element = $(this);\r\n var decimals = parseInt(element.attr(\"decimals\"), 10);\r\n var formula = element.attr(\"formule\");\r\n var id = element.attr(\"name\");\r\n\r\n formulaCache[id] = {\r\n id: id,\r\n element: element,\r\n decimals: decimals,\r\n formula: formula\r\n };\r\n });\r\n}\r\n\r\nfunction initCalc() {\r\n initFormulaCache();\r\n\r\n for (var formulaId in formulaCache) {\r\n var formula = formulaCache[formulaId];\r\n calculateFormula(formula);\r\n }\r\n}\r\n\r\nvar erroredFormulas = [];\r\n\r\nfunction handleFormulaError(id, exception) {\r\n if (erroredFormulas.indexOf(id) > -1) {\r\n return;\r\n }\r\n\r\n erroredFormulas.push(id);\r\n\r\n core.notify(\"error\", resources.formulaErrorMessage);\r\n console.error(\"Error calculating formula. Id \" + id, exception);\r\n}\r\n\r\n/*\r\n * Rights\r\n */\r\nvar rightActions = {\r\n deny: 0,\r\n allow: 1,\r\n forcedDeny: 2\r\n};\r\n\r\nfunction validateRights(triggerId, val, isCheckbox) {\r\n var triggeredRights = rights[triggerId];\r\n\r\n if (!triggeredRights) {\r\n return [];\r\n }\r\n\r\n var checkedRights = triggeredRights.map(function (right) {\r\n var matched = false;\r\n if (isCheckbox) {\r\n var values = val.split(\";\");\r\n\r\n for (var i = 0; i < values.length; i++) {\r\n matched = validation.validateValueCompare({ leftValue: values[i], operator: right.operator, rightValue: right.value });\r\n if (matched) {\r\n break;\r\n }\r\n }\r\n }\r\n else {\r\n matched = validation.validateValueCompare({ leftValue: val, operator: right.operator, rightValue: right.value });\r\n }\r\n\r\n var newAction = right.action;\r\n if (!matched) {\r\n if (right.action === rightActions.allow) {\r\n newAction = rightActions.deny;\r\n }\r\n else {\r\n newAction = rightActions.allow;\r\n }\r\n }\r\n\r\n return {\r\n uniqueTargetId: right.targetType + \"-\" + right.targetId,\r\n targetId: right.targetId,\r\n targetType: right.targetType,\r\n right: right.right,\r\n action: newAction\r\n };\r\n });\r\n\r\n var toDelete = checkedRights.reduce(function (accu, right) {\r\n if (right.action === rightActions.forcedDeny) {\r\n accu.forcedDeny.push(right.uniqueTargetId);\r\n }\r\n\r\n if (right.action === rightActions.allow) {\r\n accu.deny.push(right.uniqueTargetId);\r\n }\r\n\r\n return accu;\r\n }, { deny: [], forcedDeny: [] });\r\n\r\n var matchedRights = checkedRights.filter(function (right) {\r\n if (right.action === rightActions.deny && toDelete.deny.indexOf(right.uniqueTargetId) > -1) {\r\n return false;\r\n }\r\n\r\n if (right.action !== rightActions.forcedDeny && toDelete.forcedDeny.indexOf(right.uniqueTargetId) > -1) {\r\n return false;\r\n }\r\n\r\n return true;\r\n });\r\n\r\n return matchedRights;\r\n}\r\n\r\nfunction getValue(sender) {\r\n var result = {\r\n id: sender.id.split(\"_\")[1],\r\n isCheckbox: false,\r\n value: \"\"\r\n };\r\n\r\n if (sender.type === \"checkbox\") {\r\n if (result.id.split(\"#\").length > 1) {\r\n $(\"input[name^=\\\"\" + result.id.split(\"#\")[0] + \"#\\\"]:checked\").each(function () {\r\n if (result.value !== \"\") {\r\n result.value += \";\";\r\n }\r\n\r\n result.value += this.id.split(\"#\")[1];\r\n });\r\n }\r\n else {\r\n if ($(sender).is(\":checked\")) {\r\n result.value = \"0\";\r\n }\r\n }\r\n\r\n result.isCheckbox = true;\r\n result.id = result.id.split(\"#\")[0];\r\n }\r\n else if (sender.type === \"radio\") {\r\n // TODO: use id instead?\r\n result.value = $(\"input[name=\\\"\" + sender.name + \"\\\"]:checked\").val();\r\n\r\n // TODO: remove this line? it shouldn't be able to do anything.\r\n result.id = result.id.split(\"_\")[0];\r\n }\r\n else {\r\n result.value = $(sender).val();\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction checkRights(sender, init) {\r\n var senderData = getValue(sender);\r\n var activeRights = validateRights(senderData.id, senderData.value, senderData.isCheckbox);\r\n\r\n if (activeRights.length === 0) {\r\n return;\r\n }\r\n\r\n for (var i = 0; i < activeRights.length; i++) {\r\n var right = activeRights[i];\r\n var affectedId = right.targetId;\r\n var rightType = right.right;\r\n var rightAction = right.action;\r\n var uniqueTargetId = right.uniqueTargetId;\r\n\r\n var affectedType = right.targetType;\r\n var isPart = affectedType === \"formpart\";\r\n var isQuestion = affectedType === \"question\";\r\n var isPartDisabled = false;\r\n\r\n if (isPart) {\r\n $trg = $(\"#FP_\" + affectedId);\r\n }\r\n else if (isQuestion) {\r\n $trg = $(\"#q-container-\" + affectedId);\r\n var parentPart = $trg.parent();\r\n isPartDisabled = parentPart.data(\"partdisabled\") === \"true\";\r\n }\r\n\r\n if (isDataCorrector) {\r\n $trg.removeClass(\"hidden\");\r\n $trg.find(\".q-input *\").removeAttr(\"disabled\");\r\n continue;\r\n }\r\n\r\n var matchedForcedDeny = forcedDenyRights[uniqueTargetId];\r\n var forcedDeny = matchedForcedDeny && matchedForcedDeny.some(function (f) {\r\n var element = document.getElementById(\"id_\" + f.sourceId);\r\n\r\n if (!element) {\r\n return false;\r\n }\r\n\r\n if (validation.validateValueCompare({ leftValue: getValue(element).value, operator: f.operator, rightValue: f.value })) {\r\n return true;\r\n }\r\n });\r\n\r\n if (rightType === 2) {\r\n if (rightAction === rightActions.allow && !forcedDeny) {\r\n $trg.removeClass(\"hidden\");\r\n\r\n if (isPart && !init) {\r\n applyEvenOddParts();\r\n }\r\n }\r\n else if (rightAction === rightActions.deny || rightAction === rightActions.forcedDeny || forcedDeny) {\r\n $trg.addClass(\"hidden\");\r\n\r\n if (isPart && !init) {\r\n applyEvenOddParts();\r\n }\r\n }\r\n }\r\n else if (rightType === 1) {\r\n if (rightAction === rightActions.allow && !isPartDisabled && !forcedDeny) {\r\n toggleQuestions($trg, true);\r\n }\r\n\r\n if (rightAction === rightActions.deny || rightAction === rightActions.forcedDeny || forcedDeny) {\r\n toggleQuestions($trg, false);\r\n }\r\n }\r\n\r\n if (isPart) {\r\n if ($trg.children(\".formrow\").not(\".hidden\").length === 0) {\r\n $trg.addClass(\"hidden\");\r\n\r\n if (!init) {\r\n applyEvenOddParts();\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\nfunction toggleQuestions(container, state) {\r\n container.find(\".q-input\").each(function () {\r\n var $q = $(this);\r\n var type = +this.getAttribute(\"data-type\");\r\n\r\n // TODO: add formula?\r\n if (type === formControls.questionTypes.text) {\r\n $q.find(\"input, button, textarea\").prop(\"disabled\", !state);\r\n }\r\n else if (type === formControls.questionTypes.checkbox || type === formControls.questionTypes.radioButton) {\r\n $q.find(\"input\").prop(\"disabled\", !state);\r\n }\r\n else if (type === formControls.questionTypes.dropdown) {\r\n $q.find(\"select\").prop(\"disabled\", !state);\r\n }\r\n else if (type === formControls.questionTypes.matrix) {\r\n toggleQuestion.matrix($q, state);\r\n }\r\n else if (type === formControls.questionTypes.dateTime) {\r\n toggleQuestion.date($q, state);\r\n }\r\n else if (type === formControls.questionTypes.attachment) {\r\n toggleQuestion.attachment($q, state);\r\n }\r\n else if (type === formControls.questionTypes.drawing) {\r\n toggleQuestion.drawing($q, state);\r\n }\r\n });\r\n}\r\n\r\nvar toggleQuestion = {\r\n date: function toggleDateInput(container, state) {\r\n var input = container.find(\"input\");\r\n var role = input.attr(\"data-role\");\r\n var type = datePickerMap[role];\r\n if (type) {\r\n input.data(type).enable(state);\r\n }\r\n else {\r\n input.prop(\"disabled\", !state);\r\n }\r\n },\r\n matrix: function toggleMatrix(container, state) {\r\n var matrixContainer = container.parent().next();\r\n\r\n matrixContainer.find(\"button\").toggleClass(\"hidden\", !state);\r\n matrixContainer.find(\".matrixView\").toggleClass(\"hidden\", state);\r\n },\r\n attachment: function toggleAttachment(container, state) {\r\n var input = container.find(\"input\");\r\n\r\n // Kendo only sets k-disabled class on wrapper and disabled state on input.\r\n // Input may not be present if value is from dataset.\r\n input.data(\"kendoUpload\")?.enable(state);\r\n input.prop(\"disabled\", !state);\r\n\r\n container.parent().next().find(\".attachments-container\").toggleClass(\"hide-attachment-buttons\", !state);\r\n },\r\n drawing: function toggleCanvas(container, state) {\r\n container.find(\".clearcanvas\").prop(\"disabled\", !state);\r\n var $canvas = container.find(\"canvas:first-of-type\");\r\n\r\n if (!$canvas.hasClass(\"lower-canvas\")) {\r\n // Doesn't work with prop.\r\n $canvas.attr(\"disabled\", !state);\r\n return;\r\n }\r\n\r\n var canvas = $canvas.get(0).fabric;\r\n canvas.isDrawingMode = state;\r\n\r\n if (!state) {\r\n canvas.forEachObject(function (o) {\r\n o.selectable = false;\r\n });\r\n }\r\n }\r\n};\r\n\r\nfunction applyEvenOddParts() {\r\n $(\".formpart\").not(\".hidden\").each(function (i) {\r\n var isOdd = (i + 1) % 2 === 1;\r\n\r\n $(this)\r\n .toggleClass(\"formpart-odd\", isOdd)\r\n .toggleClass(\"formpart-even\", !isOdd);\r\n });\r\n}\r\n\r\nfunction initRights() {\r\n rightsInitOrder.forEach(function (id) {\r\n var rightsTrigger = document.getElementById(\"id_\" + id);\r\n if (!rightsTrigger) {\r\n // Radio button.\r\n rightsTrigger = document.querySelector('input[id^=\\'id_' + id + '_\\']');\r\n }\r\n\r\n if (!rightsTrigger) {\r\n // Checkbox.\r\n rightsTrigger = document.querySelector('input[id^=\\'id_' + id + '#\\']');\r\n }\r\n\r\n checkRights(rightsTrigger, true);\r\n });\r\n\r\n applyEvenOddParts();\r\n}\r\n\r\n/*\r\n * Dataset\r\n */\r\nfunction lookupDataset(questionId) {\r\n var questionInput = $(\"#id_\" + questionId);\r\n var value = questionInput.val();\r\n var lineId = null;\r\n\r\n if (!value) {\r\n return;\r\n }\r\n\r\n loadDatasetRow(\r\n questionId,\r\n value,\r\n lineId,\r\n function () {\r\n getDataset(questionId, value);\r\n }\r\n );\r\n}\r\n\r\nfunction getDataset(questionId, filter) {\r\n $.ajax({\r\n url: baseUrl + \"/api/SessionCheck/including-anonymous\",\r\n type: \"GET\",\r\n success: function () {\r\n var formId = isMatrix ? parent.FORMID : FORMID;\r\n var formTypeId = isMatrix ? parent.FORMTYPEID : FORMTYPEID;\r\n var isNewForm = formId === \"-1\";\r\n var id = isNewForm ? formTypeId : formId;\r\n var url = baseUrl + \"/Dialogs/ShowData?isNewForm=\" + isNewForm.toString() + \"&id=\" + id + \"&questionId=\" + questionId + \"&filter=\" + (filter ? encodeURIComponent(filter) : \"\");\r\n var dialog = $(\"
    \");\r\n $(\"#windowcontainer\").append(dialog);\r\n $(\"body\").css(\"overflow\", \"hidden\");\r\n $(\"#dialog\").kendoWindow({\r\n visible: false,\r\n height: leanformsNext.getPopupHeight(700),\r\n width: leanformsNext.getPopupWidth(900),\r\n title: \"Data\",\r\n modal: true,\r\n close: function () {\r\n $(\"body\").css(\"overflow\", \"\");\r\n\r\n var keyField = $(\"#id_\" + questionId);\r\n if (keyField.val() !== keyField.data(\"selected\")) {\r\n keyField.val(keyField.data(\"selected\"));\r\n }\r\n },\r\n iframe: true,\r\n content: url,\r\n deactivate: function () {\r\n this.destroy();\r\n }\r\n }).data(\"kendoWindow\").center().open();\r\n },\r\n error: function (xhr) {\r\n if (xhr.readyState !== 0) {\r\n return leanformsNext.handleError();\r\n }\r\n\r\n core.notify(\"warn\", resources.noNetworkMessage);\r\n }\r\n });\r\n}\r\n\r\nfunction loadDatasetRow(questionId, value, lineId, callback) {\r\n if (!value) {\r\n // Do not call the controller when no value is provided.\r\n return;\r\n }\r\n\r\n var formId = isMatrix ? parent.FORMID : FORMID;\r\n var formTypeId = isMatrix ? parent.FORMTYPEID : FORMTYPEID;\r\n var isNewForm = formId === \"-1\";\r\n var id = isNewForm ? formTypeId : formId;\r\n\r\n $.ajax({\r\n url: baseUrl + \"/api/datasets/row-values?isNewForm=\" + isNewForm.toString() + \"&id=\" + id + \"&questionId=\" + questionId + \"&lineId=\" + (lineId ? lineId : -1) + \"&selectedValue=\" + encodeURIComponent(value),\r\n error: leanformsNext.onAjaxError,\r\n success: function (response) {\r\n if (response.found !== 1) {\r\n if (typeof callback === \"function\") {\r\n callback();\r\n return;\r\n }\r\n }\r\n\r\n // Correct casing for manually entered value.\r\n if (response.keyValue !== value) {\r\n $(\"#id_\" + questionId).val(response.keyValue).trigger(\"change\");\r\n }\r\n\r\n // Store successfully selected value for undoing.\r\n $(\"#id_\" + questionId).data(\"selected\", response.keyValue);\r\n\r\n loadData({\r\n isDataset: true,\r\n data: response.fields,\r\n datasetData: {\r\n keyFieldId: response.keyFieldId,\r\n formGuid: response.formGuid\r\n }\r\n });\r\n }\r\n });\r\n}\r\n\r\nfunction closeDatasetDialog(questionId, value, lineId) {\r\n // Called from ShowData.\r\n $(\"#dialog\").data(\"kendoWindow\").close();\r\n $(\"#id_\" + questionId).val(value).trigger(\"change\");\r\n\r\n loadDatasetRow(questionId, value, lineId);\r\n}\r\n\r\n/*\r\n * Drawing\r\n */\r\nvar canvasMaxWidth = 500;\r\nvar canvases = [];\r\n\r\nfunction onResize() {\r\n formWidth = $(document).width();\r\n canvases.forEach(resizeCanvas);\r\n}\r\n\r\nfunction resizeCanvas(canvas) {\r\n var xsWidth = 768;\r\n var parentPadding = 30;\r\n var formPadding = 40;\r\n\r\n var currentMaxWidth = formWidth - formPadding;\r\n var parentWidth = canvas.wrapperEl.parentNode.offsetWidth;\r\n \r\n // parentWidth will be 0 if it is hidden. Fall back to max width in that case. May be a couple pixels off, but we're accepting that.\r\n var width = currentMaxWidth < xsWidth || parentWidth === 0 ? currentMaxWidth : parentWidth - parentPadding;\r\n\r\n if (width > canvasMaxWidth) {\r\n width = canvasMaxWidth;\r\n }\r\n\r\n var canvasWidth = canvas.getWidth();\r\n var ratio = canvasWidth / canvas.getHeight();\r\n var height = width / ratio;\r\n\r\n var scale = width / canvasWidth;\r\n var zoom = canvas.getZoom();\r\n\r\n zoom *= scale;\r\n\r\n canvas.setDimensions({ width: width, height: height });\r\n canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);\r\n}\r\n\r\n// Use a passive event listener for performance. Throttling could be used for further performance improvements.\r\nwindow.addEventListener(\"resize\", onResize, { passive: true });\r\n\r\nfunction setBackground(canvas) {\r\n var rect = new fabric.Rect({\r\n x: 1,\r\n y: 1,\r\n fill: \"#FFF\",\r\n stroke: \"#CCC\",\r\n strokeWidth: 1,\r\n width: canvasMaxWidth - 1,\r\n height: 299,\r\n selectable: false\r\n });\r\n\r\n canvas.add(rect);\r\n}\r\n\r\nfunction clearCanvas(id) {\r\n $(\"#id_\" + id).val(\"\");\r\n delete changedDrawings[id];\r\n\r\n var canvas = document.getElementById(\"C_\" + id).fabric;\r\n\r\n canvas.clear();\r\n setBackground(canvas);\r\n canvas.renderAll();\r\n}\r\n\r\nfunction createCanvas(id, readOnly) {\r\n $(function () {\r\n setTimeout(function () { delayedCreateCanvas(id, readOnly) }, 0);\r\n });\r\n}\r\n\r\nfunction delayedCreateCanvas(id, readOnly) {\r\n var canvasEl = document.getElementById(id);\r\n var $canvasEl = $(canvasEl);\r\n\r\n // Doesn't work with prop.\r\n var isDisabled = $canvasEl.attr(\"disabled\");\r\n var state = $canvasEl.data(\"state\");\r\n var drawingData = $canvasEl.data(\"drawing\");\r\n var canDraw = state !== \"saved\" && !readOnly;\r\n var hasDrawing = Boolean(drawingData);\r\n\r\n var canvasConfig = {\r\n width: canvasMaxWidth,\r\n height: 300,\r\n renderOnAddRemove: false,\r\n hoverCursor: \"default\",\r\n selection: false,\r\n isDrawingMode: canDraw && !isDisabled\r\n };\r\n\r\n var canvas = new fabric.Canvas(id, canvasConfig);\r\n canvasEl.fabric = canvas;\r\n\r\n canvases.push(canvas);\r\n resizeCanvas(canvas);\r\n\r\n if (hasDrawing) {\r\n drawJSONOnCanvas(canvas, drawingData);\r\n }\r\n else {\r\n setBackground(canvas);\r\n canvas.renderAll();\r\n }\r\n\r\n if (canDraw) {\r\n canvas.on(\"mouse:down\", onCanvasMouseDown);\r\n canvas.on(\"path:created\", onCanvasPathCreate);\r\n }\r\n}\r\n\r\nfunction drawJSONOnCanvas(canvas, json) {\r\n canvas.loadFromJSON(json, function canvasLoaded() {\r\n canvas.renderAll();\r\n }, function parseDrawing(jsonObject, fabricObject) {\r\n fabricObject.set(\"selectable\", false);\r\n });\r\n}\r\n\r\nfunction onCanvasMouseDown(e) {\r\n if (!this.isDrawingMode) {\r\n return;\r\n }\r\n\r\n $(\".form-fields\").bind(\"touchmove\", function (e) {\r\n e.preventDefault();\r\n });\r\n}\r\n\r\nfunction onCanvasPathCreate(e) {\r\n $(\".form-fields\").unbind(\"touchmove\");\r\n\r\n const questionId = this.lowerCanvasEl.id.split(\"_\")[1];\r\n const dataInput = document.querySelector(\"#id_\" + questionId);\r\n const json = JSON.stringify(this);\r\n\r\n // Store.\r\n dataInput.value = json;\r\n changedDrawings[questionId] = this.toDataURL(\"jpeg\");\r\n\r\n // When required, now data is set, thus hide the error.\r\n if (dataInput.hasAttribute(\"required\")) {\r\n validation.hideError(dataInput);\r\n }\r\n\r\n // Redraw.\r\n drawJSONOnCanvas(this, json);\r\n}\r\n\r\n/*\r\n * Matrix table\r\n */\r\nfunction redrawMatrix(dataInputId) {\r\n var dataInput = $(\"#\" + dataInputId);\r\n\r\n var matrixQuestionId = +dataInput.attr(\"name\");\r\n var allowDuplicateLine = dataInput.attr(\"allow-duplicate\") === \"true\";\r\n var localAttachments = matrixAttachments[matrixQuestionId] || {};\r\n\r\n var matrixData = dataInput.val();\r\n if (matrixData && matrixData.indexOf(\"<\") !== 0) {\r\n matrixData = core.xmlDecode(matrixData);\r\n }\r\n\r\n var matrix = leanformsNext.loadXml(\"\" + matrixData + \"\");\r\n var lines = $(matrix).find(\"line\");\r\n var lineCount = lines.length;\r\n\r\n var tableId = dataInputId.replace(\"md_\", \"id_\");\r\n var table = $(\"#\" + tableId);\r\n var headerCells = table.closest(\".matrix-div\").find(\"th\");\r\n var hasCode = headerCells.eq(0).attr(\"fldid\") === \"code\";\r\n\r\n dataInput.parent().find(\".matrixRemove, .matrixEdit, .matrixView\").prop(\"disabled\", true);\r\n table.find(\"tbody > tr\").remove();\r\n\r\n headerCells.each(function () {\r\n var $this = $(this);\r\n\r\n if ($this.attr(\"tot\") != \"\") {\r\n $this.attr(\"tot\", \"0\");\r\n }\r\n });\r\n\r\n // Is matrix editing/adding allowed?\r\n const matrixIsEnabled = dataInput.parent().find(\".matrixAdd\").is(\":visible\");\r\n\r\n for (var ri = 0; ri < lineCount; ri++) {\r\n var line = lines.eq(ri);\r\n var lineId = line.attr(\"id\");\r\n var lineAttachments = localAttachments[lineId];\r\n\r\n var lineNumber = ri + 1;\r\n var duplicateButton = \"\";\r\n\r\n // May we duplicate the last line?\r\n if (matrixIsEnabled && allowDuplicateLine && lineNumber === lineCount) {\r\n duplicateButton = \"\";\r\n }\r\n\r\n var row = \"\";\r\n \r\n if (!hasCode) {\r\n row += \"\" + lineNumber + duplicateButton + \"\";\r\n }\r\n\r\n headerCells.each(function () {\r\n row += getMatrixCell(this, line, lineAttachments).outerHTML;\r\n });\r\n\r\n row += \"\";\r\n table.find(\"tbody\").append(row);\r\n }\r\n\r\n var hasTotal = false;\r\n var totalRow = \"\" + resources.totalLabel + \"\";\r\n\r\n headerCells.each(function (index) {\r\n if (index === 0) {\r\n return;\r\n }\r\n\r\n var header = $(this);\r\n var calc = header.attr(\"calc\");\r\n var value = \"\";\r\n if (calc > 0) {\r\n hasTotal = true;\r\n\r\n var decimals = header.attr(\"dec\");\r\n var total = header.attr(\"tot\");\r\n var isAverage = calc === \"3\";\r\n\r\n if (isAverage) {\r\n value = total / lineCount;\r\n }\r\n else {\r\n value = Number(total);\r\n }\r\n\r\n if (parseInt(decimals) >= 0) {\r\n value = value.toFixed(decimals);\r\n }\r\n }\r\n\r\n totalRow += \"\" + value + \"\";\r\n });\r\n\r\n if (hasTotal && lineCount) {\r\n totalRow += \"\";\r\n\r\n table.find(\"tbody\").append(totalRow);\r\n }\r\n\r\n var matrixGrid = table.data(\"kendoGrid\");\r\n\r\n if (!matrixGrid) {\r\n matrixGrid = table.kendoGrid(matrixConnectedFormsConfig).data(\"kendoGrid\");\r\n }\r\n \r\n for (var i = 0; i < headerCells.length; i++) {\r\n var header = headerCells.eq(i);\r\n\r\n matrixGrid.showColumn(i);\r\n\r\n if (header.hasClass(\"HiddenColumn\") || header.hasClass(\"rights-hidden-column\")) {\r\n // Only works correctly if we show the column first.\r\n matrixGrid.hideColumn(i);\r\n }\r\n }\r\n \r\n // matrixGrid.refresh();\r\n\r\n if ($(\"#grdActions\")) {\r\n refreshActionGrid();\r\n }\r\n}\r\n\r\nfunction getMatrixCell(th, line, localAttachments) {\r\n var $th = $(th);\r\n var thid = $th.attr(\"fldid\");\r\n var calc = +$th.attr(\"calc\");\r\n var format = $th.attr(\"format\");\r\n var cell = document.createElement(\"td\");\r\n\r\n if ($th.hasClass(\"rights-hidden-column\")) {\r\n return cell;\r\n }\r\n\r\n if (!thid) {\r\n return \"\";\r\n }\r\n\r\n var field = line.find(\"field[vraagid=\\\"\" + thid + \"\\\"]\");\r\n var val = field.attr(\"display\");\r\n\r\n if (!val) {\r\n val = \"\";\r\n\r\n var isCheckbox = $th.data(\"is-checkbox\");\r\n if (isCheckbox) {\r\n if (field.attr(\"value\") === \"on\") {\r\n // Before v3.4.? this was not set correctly, so it is corrected here,\r\n val = \"[X]\";\r\n }\r\n else {\r\n // Checkbox.\r\n var fields = $(line).find(\"[vraagid^=\\\"\" + thid + \"#\\\"]\");\r\n var vals = $.map(fields, function (field) {\r\n return $(field).attr(\"display\");\r\n });\r\n\r\n val = vals.join(\"\\n\");\r\n }\r\n }\r\n }\r\n\r\n if (!val) {\r\n // TODO: Create elements instead of string.\r\n var attachments = \"\";\r\n\r\n if (localAttachments) {\r\n attachments = localAttachments\r\n .filter(function (attachment) {\r\n return attachment.questionId === thid;\r\n })\r\n .map(function (attachment) {\r\n var displayName = attachment.displayName;\r\n\r\n if (attachment.guid || attachment.fileName) {\r\n var url = baseUrl + \"/api/attachments\";\r\n if (attachment.fileName) {\r\n url += \"?fileName=\" + attachment.fileName;\r\n }\r\n else {\r\n url += \"?isNew=true&guid=\" + attachment.guid;\r\n }\r\n\r\n url += \"&displayName=\" + encodeURIComponent(displayName);\r\n\r\n return \"
    \" + core.htmlEncode(displayName) + \"\";\r\n }\r\n\r\n return core.htmlEncode(displayName);\r\n })\r\n .join(\"
    \");\r\n }\r\n else {\r\n var files = $(line).find(\"file[id=\\\"\" + thid + \"\\\"]\");\r\n files.each(function () {\r\n var file = $(this);\r\n var filename = file.attr(\"name\");\r\n var displayName = file.attr(\"displayname\");\r\n var filePath = \"/api/attachments?fileName=\" + encodeURIComponent(filename);\r\n\r\n if (displayName) {\r\n filePath += \"&displayName=\" + encodeURIComponent(displayName);\r\n }\r\n\r\n if (!displayName) {\r\n displayName = filename.split(\"_\").slice(2).join(\"_\");\r\n }\r\n\r\n if (attachments) {\r\n attachments += \"
    \";\r\n }\r\n\r\n attachments += \"\" + core.htmlEncode(displayName) + \"\";\r\n });\r\n }\r\n\r\n cell.innerHTML = attachments;\r\n }\r\n\r\n if (val && calc) {\r\n var newTot = calculateTotal($th, val, calc);\r\n\r\n $th.attr(\"tot\", newTot);\r\n }\r\n\r\n if (format === \"email\") {\r\n var mailA = document.createElement(\"a\");\r\n mailA.href = \"mailto:\" + val;\r\n mailA.textContent = val;\r\n\r\n cell.appendChild(mailA);\r\n }\r\n else if (format === \"link\") {\r\n var a = document.createElement(\"a\");\r\n a.target = \"_blank\";\r\n a.rel = \"noreferrer\";\r\n a.textContent = val;\r\n\r\n var url = field.attr(\"url\");\r\n\r\n if (url) {\r\n a.href = url;\r\n } else if (/^(http:|https:)?\\/\\//i.test(val)) {\r\n a.href = val;\r\n }\r\n\r\n cell.appendChild(a);\r\n }\r\n else if (format === \"drawing\") {\r\n var text = val ? resources.viewForm.drawing : resources.viewForm.noDrawing;\r\n var label = document.createElement(\"i\");\r\n label.textContent = text;\r\n\r\n cell.appendChild(label);\r\n }\r\n else if (val) {\r\n cell.textContent = val;\r\n }\r\n\r\n return cell;\r\n}\r\n\r\nfunction calculateTotal($th, val, calc) {\r\n var currentTotal = parseFloat($th.attr(\"tot\"));\r\n\r\n // Sum or average.\r\n if (calc === 1 || calc === 3) {\r\n return currentTotal + parseFloat(val);\r\n }\r\n\r\n // Subtract.\r\n if (calc === 2) {\r\n return currentTotal - parseFloat(val);\r\n }\r\n}\r\n\r\n/*\r\n * Load form data\r\n */\r\nasync function loadData(options) {\r\n /*\r\n * Options contains the following data structure.\r\n * {\r\n * guid: \"string\",\r\n * matrix: {\r\n * dataInputId: \"string\",\r\n * index: integer\r\n * },\r\n * data: XML || Array[{ name: \"string\", value: \"string\" }],\r\n * isFieldCopy: boolean,\r\n * isCopy: boolean,\r\n * isOffline: boolean,\r\n * isDataset: boolean,\r\n * datasetData: {\r\n * keyFieldId: integer,\r\n * formGuid: \"string\"\r\n * }\r\n * }\r\n */\r\n let formatIsXml = true;\r\n let shouldRunEvents = false;\r\n let missingDropdownOption = false;\r\n const isFieldCopy = options.isFieldCopy;\r\n const isCopy = options.isCopy;\r\n const isOffline = options.isOffline;\r\n const isDataset = options.isDataset;\r\n let datasetField = null;\r\n\r\n let timeSpent = performance.now();\r\n\r\n let el;\r\n let att;\r\n\r\n if (options.matrix) {\r\n // Matrix line.\r\n const dataInput = window.parent.$(\"#\" + options.matrix.dataInputId);\r\n\r\n const fullMatrixXml = leanformsNext.loadXml(\"\" + dataInput.val() + \"\");\r\n const matrixLineXml = fullMatrixXml.find(\"line\").eq(options.matrix.index);\r\n\r\n matrixQuestionId = +dataInput.attr(\"name\");\r\n matrixLineId = +matrixLineXml.attr(\"id\");\r\n\r\n var localAttachments = parent.matrixAttachments[matrixQuestionId] || {};\r\n var lineAttachments = localAttachments[matrixLineId];\r\n if (lineAttachments) {\r\n loadMatrixLocalAttachments(lineAttachments);\r\n }\r\n\r\n el = matrixLineXml.find(\"field\");\r\n att = matrixLineXml.find(\"file\");\r\n\r\n isSurvey = parent.isSurvey;\r\n if (isSurvey) {\r\n shouldRunEvents = true;\r\n }\r\n }\r\n else if (isFieldCopy) {\r\n shouldRunEvents = true;\r\n formatIsXml = false;\r\n\r\n el = options.data;\r\n }\r\n else if (isCopy) {\r\n shouldRunEvents = true;\r\n\r\n const xmlData = $(options.data);\r\n el = xmlData.find(\"field\");\r\n att = xmlData.find(\"file\");\r\n }\r\n else if (isDataset) {\r\n shouldRunEvents = true;\r\n formatIsXml = false;\r\n\r\n el = options.data;\r\n\r\n // Used when dataset is based on LeanForms reports.\r\n datasetField = options.datasetData.keyFieldId;\r\n\r\n const datasetGuidInput = $(\"#ds_\" + datasetField);\r\n\r\n if (datasetGuidInput.length > 0) {\r\n datasetGuidInput.val(options.datasetData.formGuid);\r\n\r\n $(\"#btn_\" + datasetField).show();\r\n }\r\n }\r\n else if (options.guid) {\r\n // Full form.\r\n showExportButtons();\r\n\r\n const formDataUrl = `${baseUrl}/api/forms/${options.guid}`;\r\n const formDataResult = await core.fetch({ url: formDataUrl });\r\n const formDataResultText = await formDataResult.text();\r\n const formDataXml = new window.DOMParser().parseFromString(formDataResultText, \"text/xml\")\r\n const $formDataXml = $(formDataXml);\r\n\r\n el = $formDataXml.find(\"field\");\r\n att = $formDataXml.find(\"file\");\r\n\r\n if ($formDataXml.find(\"form\").attr(\"issurvey\")) {\r\n isSurvey = true;\r\n shouldRunEvents = true;\r\n }\r\n }\r\n else if (isOffline) {\r\n // Full form not synced.\r\n formatIsXml = false;\r\n\r\n // Id's are numerical, contain # for check boxes.\r\n var questionIdRegex = /^[0-9#]+$/;\r\n\r\n // TODO: Should file_ inputs be loaded while offline? (only used for copying attachments?).\r\n el = options.data.filter(function (question) {\r\n return questionIdRegex.test(question.name) || question.name === \"Code\" || question.name.startsWith(\"file_\");\r\n });\r\n }\r\n\r\n for (var idx = 0; idx < el?.length; idx++) {\r\n try {\r\n var md = null;\r\n var questionInput;\r\n var question = el[idx];\r\n var questionId;\r\n var questionValue;\r\n var questionAttachmentsFromDataset;\r\n\r\n if (formatIsXml) {\r\n questionId = question.getAttribute(\"vraagid\");\r\n questionValue = question.getAttribute(\"value\");\r\n questionAttachmentsFromDataset = question.getAttribute(\"attachments\");\r\n }\r\n else {\r\n questionId = question.name;\r\n questionValue = question.value;\r\n questionAttachmentsFromDataset = question.attachments;\r\n }\r\n\r\n if (questionId === \"Code\") {\r\n $(\"#FormId\").val(questionValue);\r\n $(\"#Code\").val(questionValue);\r\n }\r\n else if (questionId.substring(0, 4) === \"tot_\") {\r\n questionInput = document.getElementById(questionId);\r\n\r\n questionInput.value = questionValue;\r\n questionInput = questionInput.previousSibling;\r\n questionInput.value = questionValue;\r\n }\r\n else if (questionId.startsWith(\"file_\") && isOffline) {\r\n // Handles copied files.\r\n questionInput = document.getElementById(questionId);\r\n questionInput.value = questionValue;\r\n\r\n if (questionValue) {\r\n questionId = questionId.slice(\"file_\".length);\r\n var explanation = $(\"
    \").addClass(\"help-block\").text(resources.copiedAttachmentsOfflineExplanation);\r\n $('#attachments_' + questionId).append(explanation);\r\n }\r\n }\r\n else {\r\n questionInput = document.getElementById(\"id_\" + questionId);\r\n if (!questionInput) {\r\n questionInput = document.getElementsByName(questionId)[0];\r\n }\r\n\r\n if (!questionInput) {\r\n // Still no input found, ignore the rest.\r\n continue;\r\n }\r\n\r\n if (questionInput.tagName === \"TABLE\") {\r\n md = \"md_\" + questionId;\r\n questionInput = document.getElementById(md);\r\n }\r\n else if (questionValue && questionInput.getAttribute(\"iscanvas\") === \"true\") {\r\n var localAttachment = lineAttachments && lineAttachments.find(function (att) {\r\n return att.questionId === questionId;\r\n });\r\n\r\n var isUnsavedMatrix = localAttachment && localAttachment.state !== \"saved\";\r\n var canEdit = isOffline || isUnsavedMatrix || isCopy;\r\n\r\n if (!canEdit) {\r\n $(\"#btn_\" + questionId).hide();\r\n }\r\n\r\n var canvasEl = $(\"#C_\" + questionId);\r\n var canvas = canvasEl.get(0).fabric;\r\n\r\n if (!canEdit) {\r\n canvasEl.data(\"state\", \"saved\");\r\n }\r\n\r\n if (canvas) {\r\n if (!canEdit) {\r\n canvas.set({\r\n hoverCursor: \"default\",\r\n isDrawingMode: false\r\n });\r\n }\r\n\r\n drawJSONOnCanvas(canvas, questionValue);\r\n }\r\n else {\r\n canvasEl.data(\"drawing\", questionValue);\r\n }\r\n\r\n if (isCopy) {\r\n changedDrawings[questionId] = canvas.toDataURL(\"jpeg\");\r\n canvasEl.data(\"state\", \"new\");\r\n }\r\n\r\n if (questionAttachmentsFromDataset?.length > 0) {\r\n var file = questionAttachmentsFromDataset[0];\r\n if (file.isDrawing && $(\"#file_\" + questionId).length == 0) {\r\n var input = $(\"\")\r\n canvasEl.after(input);\r\n }\r\n\r\n $(\"#file_\" + questionId).val(getCopiedFileObject(file.fileName, null, datasetField, true, false));\r\n }\r\n }\r\n\r\n const questionType = questionInput.type;\r\n const dateType = questionInput.getAttribute(\"datetype\");\r\n\r\n if (questionType === \"checkbox\") {\r\n if (questionValue === \"on\") {\r\n questionInput.checked = true;\r\n }\r\n }\r\n else if (questionType === \"radio\") {\r\n var selectRadioId = \"id_\" + questionInput.name + \"_\" + questionValue;\r\n questionInput = document.getElementById(selectRadioId);\r\n\r\n questionInput.checked = true;\r\n }\r\n else if (questionType === \"select-one\") {\r\n var $dropdown = $(questionInput);\r\n var optionExists = $dropdown.find(\"option[value=\\\"\" + questionValue + \"\\\"]\").length;\r\n\r\n if (isCopy && !optionExists) {\r\n missingDropdownOption = true;\r\n }\r\n\r\n if (isFieldCopy && !optionExists && $dropdown.is(\"[readonly]\")) {\r\n // readonly attribute is not supported for