@rangertechnologies/ngnxt
Version:
This library was used for creating dymanic UI based on the input JSON/data
788 lines • 195 kB
JavaScript
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,