UNPKG

@rangertechnologies/ngnxt

Version:

This library was used for creating dymanic UI based on the input JSON/data

788 lines 195 kB
import { Component, Input, Output, EventEmitter, Inject } from '@angular/core'; import { ChangeService } from '../../services/change.service'; import { ErrorWrapper } from '../../model/errorWrapper'; import { CommonModule, DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import { FormsModule } from '@angular/forms'; import { ImageCropperComponent } from '../../components/image-cropper/component/image-cropper.component'; import { CustomRadioComponent } from '../../components/custom-radio/custom-radio.component'; import { FileUploadComponent } from '../../components/file-upload/file-upload.component'; import { CustomDropdownComponent } from '../../components/custom-dropdown/custom-dropdown.component'; import { SearchBoxComponent } from '../../components/search-box/search-box.component'; import { NxtDatatable } from '../../components/datatable/datatable.component'; import { PickLocationComponent } from '../../components/pick-location/pick-location.component'; import { CustomModelComponent } from '../../components/custom-model/custom-model.component'; import { CustomCalendarComponent } from '../../components/custom-calendar/custom-calendar.component'; import { I18nPipe } from '../../i18n.pipe'; import { MatTooltipModule } from '@angular/material/tooltip'; import { NxtInput } from '../../components/nxt-input/nxt-input.component'; import { NxtButtonComponent } from '../../components/button/nxt-button.component'; import * as i0 from "@angular/core"; import * as i1 from "../../services/salesforce.service"; import * as i2 from "../../services/data.service"; import * as i3 from "../../services/change.service"; import * as i4 from "../../services/storage.service"; import * as i5 from "../../i18n.service"; import * as i6 from "@angular/common"; import * as i7 from "@angular/material/tooltip"; import * as i8 from "@angular/cdk/bidi"; export class QuestionbookComponent { sfService; dataService; changeService; storageService; i18nService; cdr; document; qbItem; questionItem; translatedQuestions = []; // VD 11Jun24 - translation changes questions; errorFieldId; labelValue; token; // VD 19JAN24 - getting token as input isEdit; handleDropDown = new EventEmitter(); handleQuestion = new EventEmitter(); // VD 19Jul01 - get updated question singleFieldChangeEmit = new EventEmitter(); dropDownData; dataBind; //@ViewChild(ComponenthostDirective, { static: true }) dynamicHost: ComponenthostDirective; subQuestions; selectedFileData = []; loadComponent$ = new BehaviorSubject(false); // HA 28DEC23 Added styling and logics to load the book type questions qbRef; // HA 19DEC23 Declaration for Salesforce Styling style; langDirection = 'ltr'; bookStyle; subscription; isImageEdit = false; selectedImageElement; transform = { translateUnit: 'px', scale: 1, rotate: 0, flipH: false, flipV: false, translateH: 0, translateV: 0 }; canvasRotation = 0; cropper; loading = false; cropperMaxHeight = 0; cropperMaxWidth = 0; cropperMinHeight = 0; cropperMinWidth = 0; cropperStaticWidth = 0; cropperStaticHeight = 0; aspectRatio = 4 / 3; roundCropper = false; imageChangedEvent = null; alignImage = 'center'; isImageHover = false; // RS 17JAN2025 //Formats and returns file upload requirements from a JSON string getFileRequirements(fieldsMeta) { try { const metaData = typeof fieldsMeta === 'object' ? fieldsMeta : JSON.parse(fieldsMeta); const fileConfig = metaData[0]; if (!fileConfig) return ''; const requirements = []; if (fileConfig.allowedTypes?.length) { requirements.push(`Accepted formats: ${fileConfig.allowedTypes.join(', ')}`); } if (fileConfig.maxFileSize) { requirements.push(`Maximum size per file: ${fileConfig.maxFileSize / 1048576}MB`); } if (fileConfig.maxFiles) { requirements.push(`Maximum number of files: ${fileConfig.maxFiles}`); } return requirements.map(req => req.trim()).join('\n'); } catch (error) { console.error('Error parsing fieldsMeta:', error); return ''; } } hadleDropDownDependent = new EventEmitter(); // VD 06Sep24 calendar changes handleCalendarDate = new EventEmitter(); handleCalendarEvent = new EventEmitter(); bookQuestionsMap = new Map(); constructor(sfService, dataService, changeService, storageService, i18nService, cdr, document) { this.sfService = sfService; this.dataService = dataService; this.changeService = changeService; this.storageService = storageService; this.i18nService = i18nService; this.cdr = cdr; this.document = document; } ngOnInit() { // VD 13Sep24 changes this.processData(); // VD sep1324 changes - process data when databind value change this.subscription = this.changeService.customChange$.subscribe((changeValue) => { if (changeValue == 'dataBind') { this.processData(); } }); } ngAfterViewInit() { // SKS20MAR25 Ensure the child component is available before calling the method // AP-26MAR25 Ensure ques.subText is always an object by parsing it if it's a string // AP-28MAR25 Add Boolean setTimeout(() => { this.questions.forEach(ques => { this.handleQues(ques); let input; if (ques.type == 'Dropdown' || ques.type == 'Radio' || ques.type == 'Location') { input = {}; input['valueObj'] = ques.input || ques.defaultValue || null; input['referenceField'] = ques.referenceField || null; input['fromQuestionId'] = ques.id || null; } else if (ques.type == 'Date' || ques.type == 'DateTime') { input = {}; input['value'] = ques.input || ques.defaultValue || null; } else if (ques.type == 'Image') { input = ques.orgImageData; } else if (ques.type === 'Boolean') { ques.input = ques.input || ques.defaultValue || false; } else { input = ques.input || ques.defaultValue || null; } this.childEventCapture(input, ques); }); }); } async handleQues(ques) { // HA 19-JAN-24 If condition is for bind-in, correction // HA 09FEB24 Adding condition to avoid undefined error if (this.dataBind && Object.keys(this.dataBind).length > 0) { let result = this.flattenObject(this.dataBind); for (let [key, val] of result) { let value = val; if (ques.referenceField === key) { if (ques.type === 'Dropdown' || ques.type === 'Radio') { // this.sqOptions.forEach(element => { // if (element['referenceField'] === key) { // element['input'] = ques.input = value || ques.defaultValue; // element['selectedValue'] = ques.selectedValue = value || ques.defaultValue; // } // }); } // HA 24JAN24 To bind-in Date and DateTime else if (ques.type === 'Date' || ques.type == 'DateTime') { ques.input = new Date(value?.toString()) || ques.defaultValue; } // HA 12FEB24 To bind-in Location Type else if (ques.type === 'Location') { ques.input = value || ques.defaultValue; ques.selectedValue = value['address'] ? value['address'] : value || ques.defaultValue; } // VD 06Sep24 calendar changes for data bindIn else if (ques.type === 'Calendar') { let entries = typeof ques['fieldsMeta'] === 'object' ? ques['fieldsMeta'] : JSON.parse(ques['fieldsMeta']); if (entries) { let referenceQuestions = []; if (entries.length > 0) { entries.forEach(element => { if (element.questionReference) { let qReference = JSON.parse(element.questionReference); referenceQuestions.push(qReference?.question); } }); } if (referenceQuestions.length > 0) { value?.forEach(calendarArray => { let date = new Date(calendarArray.date); calendarArray.entries.forEach(inputEntry => { for (let [k, v] of Object.entries(inputEntry)) { referenceQuestions.forEach(field => { if (field.referenceField == k) { if (field.type === 'Date' || field.type == 'DateTime' || field.type == 'Time') { field.input = new Date(v?.toString()) || ques.defaultValue; } if (field.type === 'Dropdown' || field.type === 'Radio') { field.selectedValue = v; field.input = v || ques.defaultValue; } else { field.input = v || ques.defaultValue; } } }); } // this.addEvent(date, referenceQuestions);//SKS 27SEP24 each event added to allEvents array }); // this.addEvent(date,referenceQuestions); //this is only last event added to allEvents array }); } } } // VD 23 Oct24 - file type changes else if (ques.type === 'File') { ques.input = []; ques.input = value; } else { ques.input = value || ques.defaultValue; } } } } else { const question = {}; question['id'] = ques.id; question['input'] = ques.input || ques.defaultValue; // HA 12FEB24 To bind-out Location Type if (ques.type === 'Location') { question['input'] = ques.input ? ques.input : ques.selectedValue; } else if (ques.type == 'File') { // VD 23 Oct24 - file type changes question['input'] = ques.input ? ques.input : ques.defaultValue || []; } question['type'] = ques.type; question['questionNumber'] = ques.questionNumber; question['referenceField'] = ques?.referenceField; question['selectedValue'] = ques?.selectedValue; // MR 31JAN24 Need to pass the selected value too } } // SKS20MAR25 flattening nested objects, mapping values using referenceField flattenObject(obj, parentKey = '', includeObjects = true) { let result = []; for (let [key, val] of Object.entries(obj)) { let newKey = parentKey ? `${parentKey}.${key}` : key; if (Array.isArray(val)) { result.push([newKey, val]); // Include full array val.forEach((item, index) => { if (typeof item === 'object' && item !== null) { result.push(...this.flattenObject(item, `${newKey}[${index}]`, false)); // Flatten objects in arrays } else { result.push([`${newKey}[${index}]`, item]); // Store primitive values in arrays } }); } else if (typeof val === 'object' && val !== null) { if (includeObjects) { result.push([newKey, val]); // Include full object } result.push(...this.flattenObject(val, newKey, false)); // Flatten nested objects } else { result.push([newKey, val]); // Store key-value pairs } } return result; } // VD 13Sep24 changes // RS 09DEC24 Changed keys processData() { // HA 19DEC23 Logic for Question Book Styling(Slaesforce) // HA 09FEB24 Added ternary if (this.qbItem?.style) { // HA 28DEC23 Added styling and logics to load the book type questions this.qbItem.style = typeof this.qbItem.style === 'string' ? JSON.parse('' + this.qbItem?.style) : this.qbItem.style; this.qbItem.style.labelClass = this.qbItem.style?.labelClass ? this.qbItem.style?.labelClass : ""; this.qbItem.style.labelStyle = this.qbItem.style?.labelStyle ? this.qbItem.style?.labelStyle : ""; this.qbItem.style.labelValueStyle = this.qbItem.style?.labelValueStyle ? this.qbItem.style?.labelValueStyle : ""; this.qbItem.style.inputClass = this.qbItem.style?.inputClass ? this.qbItem.style?.inputClass : ""; this.qbItem.style.inputStyle = this.qbItem.style.inputStyle ? this.qbItem.style?.inputStyle : ""; this.qbItem.style.bookStyle = this.qbItem.style?.bookStyle ? this.qbItem.style?.bookStyle : ""; this.qbItem.style.showLabel = this.qbItem.style?.showLabel ? this.qbItem.style?.showLabel : true; } else { if (this.qbItem) { this.qbItem.style = { "labelClass": "", "labelStyle": "", "labelValueStyle": "", "inputClass": "", "inputStyle": "", "bookStyle": "", "showLabel": true, "direction": "ltr", "questionStyle": "" }; } } this.qbItem.style.questionStyle = this.qbItem?.style?.questionStyle ? this.qbItem?.style.questionStyle : ""; this.langDirection = this.qbItem.style.direction = this.qbItem?.style?.direction ? this.qbItem?.style?.direction : 'ltr'; this.bookStyle = this.qbItem?.style?.bookStyle; this.questions.forEach(element => { var htmlElement = element?.questionText; var textOnly = htmlElement?.replace(/<[^>]*>/g, ''); element.questionText = textOnly; // HA 19DEC23 Logic for Question Styling(Slaesforce) // HA 28DEC23 Added styling and logics to load the book type questions if (element.type === 'Book') { if (element['qbReferenceQuestions']) { var qb = typeof element['qbReferenceQuestions'] === 'object' ? element['qbReferenceQuestions'] : JSON.parse(element['qbReferenceQuestions']); if (qb['questionbook'].style) { qb['questionbook'].style = typeof qb['questionbook'].style === 'string' ? JSON.parse('' + qb['questionbook'].style) : qb['questionbook'].style; } else { qb['questionbook'].style = this.qbItem.style; } // Book type question styling qb['questionbook'].style.labelClass = qb['questionbook'].style?.labelClass ? qb['questionbook'].style?.labelClass : ""; qb['questionbook'].style.labelStyle = qb['questionbook'].style?.labelStyle ? qb['questionbook'].style?.labelStyle : ""; qb['questionbook'].style.labelValueStyle = qb['questionbook'].style?.labelValueStyle ? qb['questionbook'].style.labelValueStyle : ""; qb['questionbook'].style.inputClass = qb['questionbook'].style?.inputClass ? qb['questionbook'].style?.inputClass : ""; qb['questionbook'].style.inputStyle = qb['questionbook'].style?.inputStyle ? qb['questionbook'].style?.inputStyle : ""; qb['questionbook'].style.bookStyle = qb['questionbook'].style?.bookStyle ? qb['questionbook'].style?.bookStyle : ""; qb['questionbook'].style.showLabel = qb['questionbook'].style?.showLabel ? qb['questionbook'].style?.showLabel : true; qb['questionbook'].style.questionStyle = qb['questionbook'].style?.questionStyle ? qb['questionbook'].style?.questionStyle : ""; qb['langDirection'] = qb['questionbook'].style.direction ? qb['questionbook'].style?.direction : this.langDirection; // Loop for each question in the book qb['questionbook']?.subQuestions?.forEach(questionFromBook => { questionFromBook.style = questionFromBook.style ? questionFromBook.style : qb['questionbook'].style; questionFromBook.style.labelClass = questionFromBook.style?.labelClass ? questionFromBook.style?.labelClass : ""; questionFromBook.style.labelStyle = questionFromBook.style?.labelStyle ? questionFromBook.style?.labelStyle : ""; questionFromBook.style.labelValueStyle = questionFromBook.style?.labelValueStyle ? questionFromBook.style?.labelValueStyle : ""; questionFromBook.style.inputClass = questionFromBook.style?.inputClass ? questionFromBook.style?.inputClass : ""; questionFromBook.style.inputStyle = questionFromBook.style?.inputStyle ? questionFromBook.style?.inputStyle : ""; questionFromBook.style.bookStyle = questionFromBook.style?.bookStyle ? questionFromBook.style?.bookStyle : ""; questionFromBook.style.showLabel = questionFromBook.style?.showLabel ? questionFromBook.style?.showLabel : true; questionFromBook.style.questionStyle = questionFromBook.style?.questionStyle ? questionFromBook.style?.questionStyle : ""; questionFromBook['langDirection'] = questionFromBook.style?.direction ? questionFromBook.style?.direction : this.langDirection; }); element['qbItem'] = qb['questionbook']; } } element.style = element.style ? typeof element.style === 'string' ? JSON.parse('' + element?.style) : element?.style : this.qbItem?.style; element.style.labelClass = element.style?.labelClass ? element.style?.labelClass : ""; element.style.labelStyle = element.style?.labelStyle ? element.style?.labelStyle : ""; element.style.labelValueStyle = element.style?.labelValueStyle ? element.style.labelValueStyle : ""; element.style.inputClass = element.style?.inputClass ? element.style?.inputClass : ""; element.style.inputStyle = element.style.inputStyle ? element.style?.inputStyle : ""; element.style.bookStyle = element.style?.bookStyle ? element.style?.bookStyle : ""; element.style.showLabel = element.style?.showLabel ? element.style?.showLabel : true; element.style.questionStyle = element.style?.questionStyle ? element.style?.questionStyle : ""; element['langDirection'] = element.style?.direction ? element.style?.direction : this.langDirection; //VD 02Aug24 dependent field show/hide changes // RS 09DEC24 Changed keys // AP-26MAR25 Ensure ques.subText is always an object by parsing it if it's a string if (element.referenceField) { element.dependentRef = element.referenceField; } if (element.subText) { element['subText'] = typeof element?.subText === 'object' ? element?.subText : JSON.parse(element['subText']); let dependencyObj = element.subText; if (dependencyObj.isDependentField) { element.isHidden = true; } // VD 07Aug24 - isDependentField change this.subscription = this.changeService.changeAnnounced$.subscribe((changeValue) => { // VD 20Aug24 - isDependentField change if (changeValue != undefined && dependencyObj.isDependentField) { if (changeValue.valueObj != undefined && changeValue.fromQuestionId == dependencyObj.sourceQuestionId) { // element.isHiddenField = true; if (dependencyObj.dependentValue == changeValue.valueObj) { element.isHidden = false; element.input = ''; element.selectedValue = undefined; if (element.dependentRef == changeValue.referenceField) { element.referenceField = changeValue.referenceField; } } else { element.isHidden = true; element.input = ''; element.selectedValue = undefined; this.hideDependentElements(element.id); // if there is no dependent selection remove the reference if (element.dependentRef == changeValue.referenceField) { element.referenceField = ''; } } } } }); this.storageService.update(element); } // sunday comment // if (element.type === 'Calendar') { // this.openCalendarComponent(element); // } }); this.subQuestions = []; this.setSubQuestions(this.questions); this.processTranslatedQuestions(); } // VD 07Aug24 - isDependentField change // AP-26MAR25 Ensure ques.subText is always an object by parsing it if it's a string hideDependentElements(elementId) { let elementsToHide = this.questions.filter(el => { if (el.subText) { el['subText'] = typeof el?.subText === 'object' ? el?.subText : JSON.parse(el['subText']); let depObj = el.subText; return depObj.sourceQuestionId == elementId; } return false; }); elementsToHide.forEach(el => { el.isHidden = true; el.input = ''; el.selectedValue = undefined; this.storageService.update(el); // Recursively hide elements dependent on this one this.hideDependentElements(el.id); }); } //AP-19MAY25 Ensure sqList is always an array by defaulting to [] clearSQError(quesId) { var sqList = this.subQuestions?.filter((item) => item.id == quesId) || []; for (var sq of sqList) { sq.error = null; } } // VD 11Jun24 - translation changes processTranslatedQuestions() { if (this.translatedQuestions && this.translatedQuestions.length > 0) { this.questions.forEach((orgQuestion) => { this.translatedQuestions.forEach((transQuestion) => { if (orgQuestion.uniqueIdentifier == transQuestion.identifier) { orgQuestion.questionText = transQuestion.label; orgQuestion.question = transQuestion.placeHolder; } }); }); } } setSubQuestions(records) { this.subQuestions.push(records); } // Capture and Process Child Event childEventCapture(event, ques) { let dropdownData = {}; this.changeService.announceChange(event); if (ques) { if (ques.type == 'Dropdown' || ques.type == 'Radio') { // HA 19JAN24 Correction of dropdown values ques.selectedObj = event.valueObj ? event.valueObj : null; ques.input = event.selectedObj ? event.selectedObj : event.valueObj; ques.selectedValue = event.valueObj ? event.valueObj : null; // emit the dropdown data to parent dropdownData['event'] = event.valueObj; dropdownData['ques'] = ques; if (dropdownData['event']) { this.handleDropDown.emit(dropdownData); } // VD 21DEC23 - dependent field change } else if (ques.type == 'Location') { ques.input = event.valueObj; ques.selectedValue = event.valueObj ? event.valueObj.address : null; } else if (ques.type == 'File') { ques.input = this.selectedFileData = event; // Handle file validation errors if (Array.isArray(event) && event.length === 0) { // No files uploaded when required ques.error = ques.errorMessage ? new ErrorWrapper() : null; } else if (Array.isArray(event) && event.length > 0) { // Files were successfully uploaded ques.error = null; } // If there were validation errors from the file upload component if (event?.validationErrors && event.validationErrors.length > 0) { ques.error = new ErrorWrapper(); ques.error.message = event.validationErrors[0]; // Use the first validation error } } else if (ques.type == 'Date' || ques.type == 'DateTime') { // HA 24JAN24 Converting Date and DateTime to UTC if (event) { let d = new Date(event); if (!isNaN(d?.getTime())) { //SKS23APR25 Check for valid date let utcString = d?.toISOString(); ques.input = utcString; } else { ques.input = ''; } } else { ques.input = ''; } } else if (ques.type == 'List') { // VD 20Aug24 handling the list type handle multiple object values ques.input = this.dataService.getValue(event?.valueObj, event?.field); } //RS 06JAN25 else if (ques.type === 'RichTextArea') { ques.input = event || null; } // AP-28MAR25 Add Boolean else if (ques.type === 'Boolean') { ques.input = event; ques.boolean = event; } // AP-10APR25 Number // AP19PAR25 -if change else is else if (ques.type === 'Number') { ques.input = Number(event); } else { ques.input = event; } //RS 06JAN25 // Validate error message - modified to handle rich text const hasError = ques.errorMessage && ((ques.type === 'File' && ques.input.length === 0) || (ques.type === 'RichTextArea' && (!ques.input || ques.input.trim() === '')) || (ques.type !== 'File' && ques.Type !== 'RichTextArea' && !ques.input)); ques.error = hasError ? new ErrorWrapper() : null; ques.error = hasError ? new ErrorWrapper() : null; // Once the right value is stored in ques.input store the ques (with input) in storageService this.storageService.update(ques); // VD 19Jul01 - get updated question this.handleQuestion.emit(ques); } if (ques?.singleFieldChange) { this.singleFieldChangeEmit.emit(ques); ques.singleFieldChange = false; } } deleteFile(fileData) { console.log('fileDATA', fileData); } getDropDown(event) { this.hadleDropDownDependent.emit(event); } // VD 06Sep24 calendar changes getCurrentCalendar(event) { this.handleCalendarDate.emit(event); } getCalendarEvent(event) { if (event) { this.handleCalendarEvent.emit(event); } } // VD 06Sep24 remove the special charecters and tags // from the questionText removeCharacters(questionText) { let updatedText = questionText?.replace(/<[^>]*>/g, ''); return updatedText; } // sunday comment // openCalendarComponent(ques) { // const viewContainerRef = this.dynamicHost.viewContainerRef; // if(viewContainerRef){ // viewContainerRef.clear(); // const componentRef = viewContainerRef.createComponent(CustomCalendarComponent); // componentRef.instance.question = ques; // componentRef.instance.dateSelected.subscribe((event: any) => this.getCurrentCalendar(event)); // componentRef.instance.eventSelected.subscribe((event:any) => this.getCalendarEvent(event)) // } // } isCalendarModalOpen = false; calendarModalTitle; calendarModalSize; calendarSaveButtonValue; referenceQuestions = []; qbRefrenceBook; modalCalendarModalFooter; openCalendarModal(event) { this.isCalendarModalOpen = true; this.qbRefrenceBook = event.qbRefrenceBook; this.referenceQuestions = event.referenceQuestions; this.calendarModalTitle = event.modalTitle; this.calendarModalSize = event.modalSize; this.calendarSaveButtonValue = event.saveButtonValue; this.modalCalendarModalFooter = event.modalFooter; } onCalendarModalSave() { this.changeService.dataChanges('calendar-modal-save'); } handleQuestionEvent(event) { console.log('handle question here'); } closeCalendarModal(event) { this.isCalendarModalOpen = false; } // SKS25MAR25 image add async fileChangeEvent(ques, event) { const file = event.target.files[0]; if (file) { try { const imageData = await this.readFileAsDataURL(file); ques.imageData = imageData; ques.orgImageData = imageData; // Initialize logo size if not already set if (!ques.imageSize) { ques.imageSize = { width: 150, height: 150 }; } await this.childEventCapture(imageData, ques); } catch (error) { console.error("Error reading file:", error); } } } readFileAsDataURL(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = (error) => reject(error); reader.readAsDataURL(file); }); } async onImageEdit(ques) { this.isImageEdit = true; this.selectedImageElement = ques; console.log("onImageEdit"); } async onImageDelete(ques) { try { ques.imageData = ''; ques.orgImageData = ''; // Initialize logo size if not already set if (!ques.imageSize) { ques.imageSize = { width: 150, height: 150 }; } await this.childEventCapture(ques.imageData, ques); } catch (error) { console.error("Error reading file:", error); } } // SKS25MAR25 image edit functions flipHorizontal() { this.transform = { ...this.transform, flipH: !this.transform.flipH }; } flipVertical() { this.transform = { ...this.transform, flipV: !this.transform.flipV }; } resetImage() { this.canvasRotation = 0; this.cropper = undefined; this.transform = { translateUnit: 'px', scale: 1, rotate: 0, flipH: false, flipV: false, translateH: 0, translateV: 0 }; } zoomOut() { this.transform = { ...this.transform, scale: this.transform.scale - .1 }; } zoomIn() { this.transform = { ...this.transform, scale: this.transform.scale + .1 }; } rotateLeft() { this.loading = true; setTimeout(() => { this.canvasRotation--; this.flipAfterRotate(); }); } rotateRight() { this.loading = true; setTimeout(() => { this.canvasRotation++; this.flipAfterRotate(); }); } moveLeft() { this.transform = { ...this.transform, translateH: this.transform.translateH - 1 }; } moveRight() { this.transform = { ...this.transform, translateH: this.transform.translateH + 1 }; } moveDown() { this.transform = { ...this.transform, translateV: this.transform.translateV + 1 }; } moveUp() { this.transform = { ...this.transform, translateV: this.transform.translateV - 1 }; } flipAfterRotate() { const flippedH = this.transform.flipH; const flippedV = this.transform.flipV; this.transform = { ...this.transform, flipH: flippedV, flipV: flippedH, translateH: 0, translateV: 0 }; } async imageCropped(event) { try { const base64 = await this.convertBlobToBase64(event.objectUrl); this.selectedImageElement.imageData = base64; await this.childEventCapture(base64, this.selectedImageElement); } catch (error) { console.error("Error in imageCropped:", error); } } cropperReady(sourceImageDimensions) { this.loading = false; } // SKS25MAR25 blob to base 64 converter convertBlobToBase64(objectUrl) { return fetch(objectUrl) .then(response => response.blob()) .then(blob => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result); reader.onerror = error => reject(error); }); }); } closeModal() { this.isImageEdit = false; } // SKS28MAR25 for nested book read readQuestions(qbId, ques) { let questions = []; // Added ternary to avoid undefined for (var sq in ques.bookQuestionsMap[qbId].subQuestions) { let q = ques.bookQuestionsMap[qbId].subQuestions[sq]; // if question is there process if (q) { questions.push(q); } } return questions; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: QuestionbookComponent, deps: [{ token: i1.SalesforceService }, { token: i2.DataService }, { token: i3.ChangeService }, { token: i4.StorageService }, { token: i5.I18nService }, { token: i0.ChangeDetectorRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: QuestionbookComponent, isStandalone: true, selector: "lib-questionbook", inputs: { qbItem: "qbItem", questionItem: "questionItem", translatedQuestions: "translatedQuestions", questions: "questions", errorFieldId: "errorFieldId", labelValue: "labelValue", token: "token", isEdit: "isEdit", dropDownData: "dropDownData", dataBind: "dataBind" }, outputs: { handleDropDown: "handleDropDown", handleQuestion: "handleQuestion", singleFieldChangeEmit: "singleFieldChangeEmit", hadleDropDownDependent: "hadleDropDownDependent", handleCalendarDate: "handleCalendarDate", handleCalendarEvent: "handleCalendarEvent" }, providers: [ChangeService], ngImport: i0, template: "<!-- HA 18JAN24 Added class for styling -->\n<div [style]=\"bookStyle\" class=\"content-box form-group\">\n <div class=\"form-row\">\n <!-- HA 20DEC23 Directive and Question Style from salesforce -->\n <!-- RA09DEC24 Changed keys-->\n <div [class]=\"'col-lg-' + ques.size + ' paddingnone'\" *ngFor=\"let ques of questions;let i = index\"\n [id]=\"ques.id\" [dir]=\"ques.langDirection\" [style]=\"ques?.style?.questionStyle\">\n <!-- VD 09May24 is hide field change-->\n <div *ngIf=\"!ques.isHidden\" class=\"nxtInputContainer\">\n <!-- AP-28MAR25 Show label if enabled and not a Boolean type -->\n <div [ngClass]=\"{ down2: qbItem?.progressBar }\"\n *ngIf=\"ques.style?.showLabel ? (ques.style?.showLabel && ques.type !== 'Boolean' && ques.type !== 'Text' && ques.type !== 'List' && ques?.type !== 'Dropdown' && ques.type !== 'RichTextArea' && ques.type !== 'DateTime' && ques.type !== 'Location' && ques.type !== 'Number' && ques.type !== 'Date' && ques.type !== 'Time' && ques.type !== 'TextArea' && ques.type !== 'Label'): (ques.style?.showLabel !== false && ques.type !== 'Boolean' && ques.type !== 'Text' && ques.type !== 'List' && ques?.type !== 'Dropdown' && ques.type !== 'DateTime' && ques.type !== 'Location' && ques.type !== 'Number' && ques.type !== 'Date' && ques.type !== 'Time' && ques.type !== 'TextArea' && ques.type !== 'Label')\"\n [style]=\"ques.style?.labelStyle\">\n <span [class]=\"'dis-flex shengel-myt-font3 myt-font7 '\" [style]=\"ques.style?.labelValueStyle\"\n [ngStyle]=\"{\n 'font-family': ques.font,\n 'color': ques.fontColor,\n 'font-size': ques.fontSize,\n 'font-weight': ques.fontWeight\n }\">{{\n removeCharacters(ques?.questionText) }}\n <div *ngIf=\"ques.isOptional && !ques.isReadOnly\" style=\"color: red;\">*</div>\n <!-- SKS13MAR25 only show on file type -->\n <!-- RS 17JAN2025 -->\n <!-- Displays icons with tooltips help text -->\n <div *ngIf=\"ques.questionText && ques?.helpText && ques.type === 'File'\" class=\"icon\"\n [matTooltip]=\"ques?.helpText\" matTooltipClass=\"white-tooltip\">i</div>\n <!-- RS 17JAN2025 -->\n <!-- Displays icons with tooltips for file requirements -->\n <div class=\"icon\" *ngIf=\"ques.fieldsMeta && ques.type === 'File'\"\n [matTooltip]=\"getFileRequirements(ques.fieldsMeta)\" matTooltipClass=\"white-tooltip\"\n style=\"margin-left: 4px;\">i</div>\n </span>\n </div>\n <!-- calendar -->\n <div *ngIf=\"ques.type === 'Calendar'\">\n <app-custom-calendar [question]=\"ques\" (eventSelected)=\"getCalendarEvent($event)\"\n (dateSelected)=\"getCurrentCalendar($event)\" (openModal)=\"openCalendarModal($event)\"\n (closeModal)=\"closeCalendarModal($event)\"></app-custom-calendar>\n <!-- model used in calendar component -->\n <app-custom-model *ngIf=\"isCalendarModalOpen\" [modalTitle]=\"calendarModalTitle\"\n [isModalOpen]=\"isCalendarModalOpen\" [modalSize]=\"calendarModalSize\"\n [saveButtonValue]=\"calendarSaveButtonValue\" [modalFooter]=\"modalCalendarModalFooter\"\n (saveButtonEmit)=\"onCalendarModalSave()\" (cancelButtonEmit)=\"closeCalendarModal($event)\">\n <lib-questionbook [qbItem]=\"qbRefrenceBook\" [questions]=\"referenceQuestions\"\n (handleQuestion)=\"handleQuestionEvent($event)\"></lib-questionbook>\n </app-custom-model>\n </div>\n\n <!-- SKS11JUN25 for pick location -->\n <app-pick-location *ngIf=\"ques.type === 'Location'\" [apiKey]=\"qbItem['apiKey']\" [address]=\"ques.selectedValue\" [question]=\"ques\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n (locationSelected)=\"childEventCapture($event, ques)\">\n </app-pick-location>\n\n <!-- SKS25MAR25 Image -->\n <div *ngIf=\"ques.type === 'Image'\" (mouseenter)=\"isImageHover = true;\"\n (mouseleave)=\"isImageHover = false;\">\n <div *ngIf=\"isImageHover\" style=\"display: flex; justify-content: end;\">\n <svg *ngIf=\"!ques.isReadOnly\" (click)=\"onImageEdit(ques)\" width=\"16\" height=\"16\"\n viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M11.1067 6.07174L9.92833 4.8934L2.16667 12.6551V13.8334H3.345L11.1067 6.07174ZM12.285 4.8934L13.4633 3.71507L12.285 2.53674L11.1067 3.71507L12.285 4.8934ZM4.035 15.5001H0.5V11.9642L11.6958 0.768403C11.8521 0.612177 12.064 0.524414 12.285 0.524414C12.506 0.524414 12.7179 0.612177 12.8742 0.768403L15.2317 3.1259C15.3879 3.28218 15.4757 3.4941 15.4757 3.71507C15.4757 3.93604 15.3879 4.14796 15.2317 4.30424L4.03583 15.5001H4.035Z\"\n fill=\"#6C757D\" />\n </svg>\n <svg *ngIf=\"!ques.isReadOnly\" (click)=\"onImageDelete(ques)\" width=\"16\" height=\"16\"\n viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14 3.98726C11.78 3.76726 9.54667 3.65393 7.32 3.65393C6 3.65393 4.68 3.7206 3.36 3.85393L2 3.98726\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path\n d=\"M5.6665 3.31362L5.81317 2.44028C5.91984 1.80695 5.99984 1.33362 7.1265 1.33362H8.87317C9.99984 1.33362 10.0865 1.83362 10.1865 2.44695L10.3332 3.31362\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path\n d=\"M12.5667 6.09375L12.1334 12.8071C12.06 13.8537 12 14.6671 10.14 14.6671H5.86002C4.00002 14.6671 3.94002 13.8537 3.86668 12.8071L3.43335 6.09375\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path d=\"M6.88647 11.0004H9.10647\" stroke=\"#FF2C10\" stroke-width=\"1.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path d=\"M6.3335 8.33325H9.66683\" stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </div>\n <img *ngIf=\"ques.isReadOnly\" [src]=\"ques.imageData\" />\n <div *ngIf=\"!ques.isReadOnly\" class=\"logo-container\">\n <!-- Logo preview area -->\n <div class=\"logo-preview\" *ngIf=\"ques.input\">\n <img [src]=\"ques.imageData\" />\n </div>\n\n <!-- Upload button -->\n <div *ngIf=\"!ques.imageData\" class=\"logo-upload-placeholder\">\n <label for=\"logo-upload-{{i}}\" class=\"logo-upload-label\">\n <img src=\"../assets/icons/Image.svg\" alt=\"Upload\" />\n <span>Upload Image</span>\n </label>\n <input type=\"file\" id=\"logo-upload-{{i}}\" accept=\"image/*\"\n (change)=\"fileChangeEvent(ques, $event)\" style=\"display: none;\" />\n </div>\n </div>\n </div>\n\n <!-- SKS21MAR25 line -->\n <hr *ngIf=\"ques.type === 'Line'\" class=\"custom-line\" style=\"display: inline-flex\" />\n <!-- SKS13MAR25 data table -->\n <nxt-datatable *ngIf=\"ques.type === 'Table'\" isEditRow isDeleteRow actionButton isButtons [question]=\"ques\" from=\"formBuilder\"\n (valueChange)=\"childEventCapture($event.data, ques); clearSQError(ques.id)\" [apiMeta]=\"ques.subText\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n [tableConfig]=\"ques.tableConfig\" tableId=\"\" direction=\"ltr\" tableWidth=\"auto\">\n </nxt-datatable>\n <!-- list -->\n <lib-search-box *ngIf=\"ques.type === 'List'\" [question]=\"ques\" [readOnly]=\"ques.isReadOnly\" [apiMeta]=\"ques.subText\"\n [id]=\"ques.id\" [placeHolderText]=\"ques.question || ''\" [filterName]=\"ques.input\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n (searchValueChange)=\"childEventCapture($event.value,$event.question)\">\n </lib-search-box>\n\n <!-- Dropdown -->\n <!-- HA 09FEB24 Added condition of sqOption to the dropdown -->\n <app-custom-dropdown *ngIf=\"ques?.type === 'Dropdown' && ques?.options\" [options]=\"ques.options\" [token]=\"token\" [apiMeta]=\"ques.subText\"\n [id]=\"ques.id\" [selectedValue]=\"qbItem.isShengel ? ques.input : ques.selectedValue\"\n placeholder=\"---{{'select' | i18n:i18nService.currentLanguage}}---\"\n [errorMessage]=\"ques.errorMessage\" [error]=\"ques.error\" [referenceField]=\"ques.referenceField\"\n [readOnly]=\"ques.isReadOnly\" [question]=\"ques\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n (valueChange)=\"childEventCapture($event.value,$event.question); clearSQError(ques.id)\">\n </app-custom-dropdown>\n <!-- // VD 02Aug24 custom-radio component -->\n <div *ngIf=\"ques.type === 'Radio' && ques?.options\" class=\"\">\n <app-custom-radio [options]=\"ques.options\" [question]=\"ques\" [token]=\"token\" [apiMeta]=\"ques.subText\" [id]=\"ques.id\"\n [selectedValue]=\"ques.selectedValue\" [errorMessage]=\"ques.errorMessage\" [error]=\"ques.error\"\n [referenceField]=\"ques.referenceField\" [readOnly]=\"ques.isReadOnly\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n (valueChange)=\"childEventCapture($event.value,$event.question); clearSQError(ques.id)\">\n </app-custom-radio>\n </div>\n\n <!-- Attachment / Files -->\n <app-file-upload *ngIf=\"ques.type === 'File'\" [limitFileUploading]=\"5\" [error]=\"ques.error\" [question]=\"ques\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n [allFiles]=\"ques.input\" (selectedFileData)=\"childEventCapture($event.value,$event.question)\"\n (deletedFileData)=\"deleteFile($event)\"\n ></app-file-upload>\n <!-- SKS11JUN25 Button -->\n <nxt-button\n *ngIf=\"ques.type === 'Button'\"\n [mode]=\"isEdit !== true ? 'view' : 'edit'\"\n [btnHeight]=\"'50px'\" [btnWidth]=\"'150px'\" [buttonValue]=\"ques?.question\"\n (buttonClickEmit)=\"childEventCapture($event, ques)\"\n >\n </nxt-button>\n <!-- SKS11JUN25 book type -->\n <lib-questionbook *ngIf=\"ques.type === 'Book'\" [qbItem]=\"ques.qbItem\" [labelValue]=\"labelValue\"\n [questions]=\"readQuestions(ques.qbReference, ques.qbReferenceQuestions)\"\n (handleDropDown)=\"getDropDown($event)\"></lib-questionbook>\n <!-- SKS11JUN25 Text, Email, label,