import { AfterViewInit, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, inject, Input, ViewChild, Renderer2, OnInit } from '@angular/core';
import { Dynamic, EventConstants, HandlerLike, HTMLFormTarget, KeyValue } from '../../engine';
import { CrudOperations, OperationKeys } from '@decaf-ts/db-decorators';
import { ForAngularModule } from '../../for-angular.module';
import { CollapsableDirective } from '../../directives/collapsable.directive';
import { IonAccordion, IonAccordionGroup, IonButton, IonItem, IonLabel, IonList, ItemReorderEventDetail, IonReorderGroup, IonReorder } from '@ionic/angular/standalone';
import { cleanSpaces, generateRandomValue, itemMapper, windowEventEmitter } from '../../helpers';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { NgxBaseComponent } from '../../engine';
import { alertCircleOutline, createOutline } from 'ionicons/icons';
import { TranslateService } from '@ngx-translate/core';
import { IFieldSetItem, IFieldSetValidationEvent } from '../../engine/interfaces';
/**
* @description Dynamic fieldset component with collapsible accordion functionality.
* @summary This component provides a sophisticated fieldset container that automatically
* adapts its behavior based on CRUD operations. It integrates seamlessly with Ionic's
* accordion components to create expandable/collapsible sections for organizing form
* content and related information. The component intelligently determines its initial
* state based on the operation type, opening automatically for READ and DELETE operations
* while remaining closed for CREATE and UPDATE operations.
*
* @example
* ```html
* <!-- Basic usage with automatic state management -->
* <ngx-decaf-fieldset
* name="Personal Information"
* [operation]="OperationKeys.READ"
* target="_self">
* <ion-input label="Name" placeholder="Enter name"></ion-input>
* <ion-input label="Email" type="email" placeholder="Enter email"></ion-input>
* </ngx-decaf-fieldset>
*
* <!-- Advanced usage with custom operation -->
* <ngx-decaf-fieldset
* name="Contact Details"
* [operation]="currentOperation"
* target="_blank">
* <!-- Complex form fields -->
* </ngx-decaf-fieldset>
* ```
*
* @mermaid
* sequenceDiagram
* participant U as User
* participant F as FieldsetComponent
* participant I as Ionic Accordion
* participant D as DOM
*
* F->>F: ngAfterViewInit()
* alt operation is READ or DELETE
* F->>F: Set isOpen = true
* F->>D: Query accordion element
* F->>I: Set value attribute to 'open'
* F->>F: Trigger change detection
* end
* U->>I: Click accordion header
* I->>F: handleChange(event)
* F->>F: Update isOpen state
* F->>I: Reflect new state
*
* @memberOf ForAngularModule
*/
@Dynamic()
@Component({
standalone: true,
selector: 'ngx-decaf-fieldset',
templateUrl: './fieldset.component.html',
styleUrls: ['./fieldset.component.scss'],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [ForAngularModule, IonAccordionGroup, IonAccordion, IonList, IonItem, IonLabel, IonReorder, IonButton, IonReorderGroup, CollapsableDirective],
host: {'[attr.id]': 'overriode '},
})
export class FieldsetComponent extends NgxBaseComponent implements OnInit, AfterViewInit {
/**
* @description Reference to the ion-accordion-group component for programmatic control.
* @summary ViewChild reference that provides direct access to the Ionic accordion group component.
* This enables programmatic control over the accordion's expand/collapse state, allowing
* the component to open/close the accordion based on validation errors, CRUD operations,
* or other business logic requirements.
*
* @type {IonAccordionGroup}
* @memberOf FieldsetComponent
*/
@ViewChild('accordionComponent', { static: false })
accordionComponent!: IonAccordionGroup;
/**
* @description The display name or title of the fieldset section.
* @summary Sets the legend or header text that appears in the accordion header. This text
* provides a clear label for the collapsible section, helping users understand what content
* is contained within. The name is displayed prominently and serves as the clickable area
* for expanding/collapsing the fieldset.
*
* @type {string}
* @default 'Child'
* @memberOf FieldsetComponent
*/
@Input()
name: string = 'Child';
/**
* @description The parent component identifier for hierarchical fieldset relationships.
* @summary Specifies the parent component name that this fieldset belongs to in a hierarchical
* form structure. This property is used for event bubbling and establishing parent-child
* relationships between fieldsets in complex forms with nested structures.
*
* @type {string}
* @default 'Child'
* @memberOf FieldsetComponent
*/
@Input()
childOf: string = 'Child';
/**
* @description The parent component identifier for hierarchical fieldset relationships.
* @summary Specifies the parent component name that this fieldset belongs to in a hierarchical
* form structure. This property is used for event bubbling and establishing parent-child
* relationships between fieldsets in complex forms with nested structures.
*
* @type {string}
* @default 'Child'
* @memberOf FieldsetComponent
*/
@Input()
override uid: string = generateRandomValue(12);
/**
* @description Custom type definitions for specialized fieldset behavior.
* @summary Defines custom data types or validation rules that should be applied to this fieldset.
* Can be a single type string or array of types that determine how the fieldset handles
* data validation, formatting, and display behavior for specialized use cases.
*
* @type {string | string[]}
* @memberOf FieldsetComponent
*/
@Input()
customTypes!: string | string[];
/**
* @description The current CRUD operation context.
* @summary Determines the component's initial behavior and state based on the current operation.
* This input is crucial for auto-state management: READ and DELETE operations automatically
* open the fieldset to show content, while CREATE and UPDATE operations keep it closed
* initially. This provides an intuitive user experience aligned with operation semantics.
*
* @type {OperationKeys}
* @default OperationKeys.READ
* @memberOf FieldsetComponent
*/
/**
* @description The CRUD operation type for the current fieldset context.
* @summary Determines the component's initial behavior and state based on the current operation.
* This input is crucial for auto-state management: READ and DELETE operations automatically
* open the fieldset to show content, while CREATE and UPDATE operations keep it closed
* initially. This provides an intuitive user experience aligned with operation semantics.
*
* @type {OperationKeys}
* @default OperationKeys.READ
* @memberOf FieldsetComponent
*/
@Input()
operation: OperationKeys = OperationKeys.READ;
/**
* @description Reactive form group associated with this fieldset.
* @summary The FormGroup instance that contains all form controls within this fieldset.
* Used for form validation, value management, and integration with Angular's reactive forms.
*
* @type {FormGroup}
* @memberOf FieldsetComponent
*/
@Input()
formGroup!: FormArray;
/**
* @description Primary title text for the fieldset content.
* @summary Display title used for fieldset identification and content organization.
* Provides semantic meaning to the grouped form fields.
*
* @type {string}
* @memberOf FieldsetComponent
*/
@Input()
title!: string;
/**
* @description Secondary descriptive text for the fieldset.
* @summary Additional information that provides context or instructions
* related to the fieldset content and purpose.
*
* @type {string}
* @memberOf FieldsetComponent
*/
@Input()
description!: string;
/**
* @description Form target attribute for nested form submissions.
* @summary Specifies where to display the response after submitting forms contained within
* the fieldset. This attribute mirrors the HTML form target behavior, allowing control over
* whether form submissions open in the same window, new window, or specific frame. Useful
* for complex form workflows and multi-step processes.
*
* @type {HTMLFormTarget}
* @default '_self'
* @memberOf FieldsetComponent
*/
@Input()
target: HTMLFormTarget = '_self';
/**
* @description Enables multiple item management within the fieldset.
* @summary Boolean flag that determines if the fieldset supports adding multiple values.
* When true, displays a reorderable list of items with add/remove functionality.
*
* @type {boolean}
* @default false
* @memberOf FieldsetComponent
*/
@Input()
multiple: boolean = false;
/**
* @description Array of raw values stored in the fieldset.
* @summary Contains the actual data values that have been added to the fieldset.
* This is the source of truth for the fieldset's data state.
*
* @type {KeyValue[]}
* @default []
* @memberOf FieldsetComponent
*/
@Input()
value: KeyValue[] = [];
/**
* @description Event handler functions for custom fieldset actions.
* @summary A record of event handler functions keyed by event names that can be triggered
* within the fieldset. These handlers provide extensibility for custom business logic
* and can be invoked for various fieldset operations and user interactions.
*
* @type {HandlerLike}
* @memberOf FieldsetComponent
*/
@Input()
handlers!: HandlerLike;
/**
* @description Array of formatted items for UI display.
* @summary Contains the processed items ready for display in the component template.
* These items are mapped from the raw values using the mapper configuration.
*
* @type {IFieldSetItem[]}
* @default []
* @memberOf FieldsetComponent
*/
items: IFieldSetItem[] = [];
/**
* @description Currently selected item for update operations.
* @summary Holds the item being edited when in update mode. Used to track
* which item is being modified and apply changes to the correct item.
*
* @type {IFieldSetItem | undefined}
* @memberOf FieldsetComponent
*/
updatingItem!: IFieldSetItem | undefined;
/**
* @description Current state of the accordion (expanded or collapsed).
* @summary Boolean flag that tracks whether the fieldset accordion is currently open or closed.
* This property is automatically managed based on user interactions and initial operation state.
* It serves as the single source of truth for the component's visibility state and is used
* to coordinate between user actions and programmatic state changes. The value is automatically
* set based on CRUD operations during initialization and updated through user interactions.
*
* @type {boolean}
* @default false
* @memberOf FieldsetComponent
*/
isOpen: boolean = false;
/**
* @description Indicates whether the fieldset contains required form fields.
* @summary Boolean flag that signals the presence of mandatory input fields within the fieldset.
* This property is automatically set by the CollapsableDirective when required fields are detected,
* and can be used to apply special styling, validation logic, or UI indicators to highlight
* fieldsets that contain mandatory information. It helps with form validation feedback and
* user experience by making required sections more prominent.
*
* @type {boolean}
* @default false
* @memberOf FieldsetComponent
*/
isRequired: boolean = false;
/**
* @description Indicates whether the fieldset contains validation errors.
* @summary Boolean flag that tracks if any form fields within the fieldset have validation errors.
* This property is used to control accordion behavior when errors are present, preventing
* users from collapsing the accordion when they need to see and address validation issues.
* It's automatically updated when validation error events are received from child form fields.
*
* @type {boolean}
* @default false
* @memberOf FieldsetComponent
*/
hasValidationErrors: boolean = false;
/**
* @description Validation error message for duplicate values.
* @summary Stores the error message when a user attempts to add a duplicate value
* to the fieldset. Used to display uniqueness validation feedback.
*
* @type {string | undefined}
* @memberOf FieldsetComponent
*/
isUniqueError: string | undefined = undefined;
/**
* @description Reference to CRUD operation constants for template usage.
* @summary Exposes the OperationKeys enum to the component template, enabling conditional
* rendering and behavior based on operation types. This protected readonly property ensures
* that template logic can access operation constants while maintaining encapsulation and
* preventing accidental modification of the enum values.
*
* @type {CrudOperations}
* @default OperationKeys.CREATE
* @memberOf FieldsetComponent
*/
protected readonly OperationKeys: CrudOperations = OperationKeys.CREATE;
/**
* @description Angular change detection service.
* @summary Injected service that provides manual control over change detection cycles.
* This is essential for ensuring that programmatic DOM changes (like setting accordion
* attributes) are properly reflected in the component's state and trigger appropriate
* view updates when modifications occur outside the normal Angular change detection flow.
*
* @private
* @type {ChangeDetectorRef}
* @memberOf FieldsetComponent
*/
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
/**
* @description Angular Renderer2 service for safe DOM manipulation.
* @summary Injected service that provides a safe, platform-agnostic way to manipulate DOM elements.
* This service ensures proper handling of DOM operations across different platforms and environments,
* including server-side rendering and web workers.
*
* @private
* @type {Renderer2}
* @memberOf FieldsetComponent
*/
private renderer: Renderer2 = inject(Renderer2);
/**
* @description Translation service for internationalization.
* @summary Injected service that provides translation capabilities for UI text.
* Used to translate button labels and validation messages based on the current locale.
*
* @private
* @type {TranslateService}
* @memberOf FieldsetComponent
*/
private translateService: TranslateService = inject(TranslateService);
/**
* @description Localized label text for action buttons.
* @summary Dynamic button label that changes based on the current operation mode.
* Shows "Add" for create operations and "Update" for edit operations.
*
* @type {string}
* @memberOf FieldsetComponent
*/
buttonLabel!: string;
/**
* @description Component constructor that initializes the fieldset with icons and component name.
* @summary Calls the parent NgxBaseComponent constructor with the component name and
* required Ionic icons (alertCircleOutline for validation errors and createOutline for add actions).
* Sets up the foundational component structure and icon registry.
*
* @memberOf FieldsetComponent
*/
constructor() {
super('FieldsetComponent', {alertCircleOutline, createOutline});
}
/**
* @description Component initialization lifecycle method.
* @summary Initializes the component by setting up repository relationships if a model exists,
* and configures the initial button label for the add action based on the current locale.
* This method ensures proper setup of translation services and component state.
*
* @returns {void}
* @memberOf FieldsetComponent
*/
ngOnInit(): void {
if(this.model)
this._repository = this.repository;
this.buttonLabel = this.translateService.instant(this.locale + '.add');
}
/**
* @description Initializes the component state after view and child components are rendered.
* @summary This lifecycle hook implements intelligent auto-state management based on the current
* CRUD operation. For READ and DELETE operations, the fieldset automatically opens to provide
* immediate access to information, while CREATE and UPDATE operations keep it closed to maintain
* a clean initial interface. The method directly manipulates the DOM to ensure proper accordion
* synchronization and triggers change detection to reflect the programmatic state changes.
*
* @mermaid
* sequenceDiagram
* participant A as Angular Lifecycle
* participant F as FieldsetComponent
* participant D as DOM
* participant C as ChangeDetector
*
* A->>F: ngAfterViewInit()
* alt operation is READ or DELETE
* F->>F: Set isOpen = true
* F->>D: Query ion-accordion-group element
* alt accordion element exists
* F->>D: Set value attribute to 'open'
* end
* end
* F->>C: detectChanges()
* C->>F: Update view with new state
*
* @returns {void}
* @memberOf FieldsetComponent
*/
ngAfterViewInit(): void {
if (this.operation === OperationKeys.READ || this.operation === OperationKeys.DELETE) {
this.isOpen = true;
// hidden remove button
const accordionElement = this.component?.nativeElement.querySelector('ion-accordion-group');
if(this.accordionComponent)
this.renderer.setAttribute(accordionElement, 'value', 'open');
} else {
const inputs = this.component?.nativeElement.querySelectorAll('[required]');
this.isRequired = inputs.length > 0;
if(this.isRequired) {
this.accordionComponent.value = 'open';
this.handleAccordionToggle();
}
}
this.changeDetectorRef.detectChanges();
}
/**
* @description Handles removal of the fieldset with slide animation.
* @summary Initiates the removal process for the fieldset with a smooth slide-up animation.
* The method applies CSS classes for the slide animation and then safely removes the
* element from the DOM using Renderer2. This provides a polished user experience
* when removing fieldset instances from dynamic forms.
*
* @param {Event} event - DOM event from the remove button click
* @returns {void}
* @memberOf FieldsetComponent
*/
handleRemoveComponent(event: Event): void {
event.stopImmediatePropagation();
this.component.nativeElement.classList.add('dcf-animation', 'dcf-animation-slide-top-medium', 'dcf-animation-reverse', 'dcf-animation-fast');
setTimeout(() => {
// Use Renderer2 to safely remove the element
const parent = this.renderer.parentNode(this.component.nativeElement);
if (parent)
this.renderer.removeChild(parent, this.component.nativeElement);
}, 150);
}
/**
* @description Handles creating new items or triggering group addition events.
* @summary Processes form validation events for item creation or emits events to trigger
* the addition of new fieldset groups. When called with validation event data, it validates
* uniqueness and adds the item to the fieldset. When called without parameters, it triggers
* a group addition event for parent components to handle.
*
* @param {CustomEvent<IFieldSetValidationEvent>} [event] - Optional validation event containing form data
* @returns {Promise<void>}
* @memberOf FieldsetComponent
*
* @example
* ```typescript
* // Called from form validation
* handleCreateItem(validationEvent);
*
* // Called to trigger group addition
* handleCreateItem();
* ```
*/
async handleCreateItem(event?: CustomEvent<IFieldSetValidationEvent>): Promise<void> {
if(event && event instanceof CustomEvent) {
event.stopImmediatePropagation();
const {formGroup, value, isValid} = event.detail;
this.formGroup = formGroup as FormArray;
if(!this.mapper)
this.mapper = this.getMapper(value as KeyValue);
if(isValid ){
this.isUniqueError = undefined;
this.buttonLabel = this.translateService.instant(this.locale + '.add');
this.setValue();
} else {
this.isUniqueError = (value as KeyValue)?.[this.pk] || undefined;
}
} else {
windowEventEmitter(EventConstants.FIELDSET_ADD_GROUP, {
component: this.component.nativeElement,
index: this.value?.length,
parent: this.childOf,
operation: !this.updatingItem ? OperationKeys.CREATE : OperationKeys.UPDATE
});
}
}
/**
* @description Handles item update operations with form state management.
* @summary Locates an item in the form array for editing and prepares the component
* for update mode. Updates the button label to reflect the edit state and stores
* the item being updated. Triggers a window event to notify parent components.
*
* @param {string | number} value - The identifier value of the item to update
* @param {number} index - The array index position of the item
* @returns {void}
* @memberOf FieldsetComponent
*/
handleUpdateItem(value: string | number, index: number): void {
const item = this.formGroup.controls.find(control => `${control.get(this.pk)?.value}`.toLowerCase() === cleanSpaces(`${value}`, true)) as FormControl;
if(item) {
this.buttonLabel = this.translateService.instant(this.locale + '.update');
this.updatingItem = Object.assign({}, item.value || {});
windowEventEmitter(EventConstants.FIELDSET_UPDATE_GROUP, {
parent: this.childOf,
component: this.component.nativeElement,
index: index
});
}
}
/**
* @description Cancels the update mode and resets the UI state.
* @summary Exits the update mode by resetting the button label and clearing the updating item,
* restoring the component to its default state for adding new items. Notifies parent components
* that the update operation has been cancelled.
*
* @returns {void}
* @memberOf FieldsetComponent
*/
handleCancelUpdateItem(): void {
this.buttonLabel = this.translateService.instant(this.locale + '.add');
this.updatingItem = undefined;
windowEventEmitter(EventConstants.FIELDSET_UPDATE_GROUP, {
parent: this.childOf,
component: this.component.nativeElement,
index: this.value?.length
});
}
/**
* @description Handles item removal operations with form array management.
* @summary Processes item removal by either handling validation events or removing specific
* items from the form array. When called with a validation event, it triggers value updates.
* When called with an identifier, it locates and removes the matching item from the form array.
*
* @param {string | undefined} value - The identifier of the item to remove
* @param {CustomEvent} [event] - Optional validation event for form updates
* @returns {void}
* @memberOf FieldsetComponent
*/
handleRemoveItem(value: string | undefined, event?: CustomEvent): void {
if(event && event instanceof CustomEvent) {
event.stopImmediatePropagation();
return this.setValue();
}
const formArray = this.formGroup as FormArray;
const arrayLength = formArray.length;
for (let index = arrayLength - 1; index >= 0; index--) {
const group = formArray.at(index) as FormGroup;
if (cleanSpaces(group.get(this.pk)?.value) === cleanSpaces(value as string)) {
windowEventEmitter(EventConstants.FIELDSET_REMOVE_GROUP, {
parent: this.childOf,
component: this.component.nativeElement,
index,
formGroup: group
});
}
}
}
/**
* @description Handles reordering of items within the fieldset list.
* @summary Processes drag-and-drop reorder events from the ion-reorder-group component.
* Updates both the display items array and the underlying value array to maintain
* consistency between UI state and data state. Preserves item indices after reordering.
*
* @param {CustomEvent<ItemReorderEventDetail>} event - Ionic reorder event containing source and target indices
* @returns {void}
* @memberOf FieldsetComponent
*
* @example
* ```html
* <ion-reorder-group (ionItemReorder)="handleReorder($event)">
* <!-- Reorderable items -->
* </ion-reorder-group>
* ```
*/
handleReorderItems(event: CustomEvent<ItemReorderEventDetail>): void {
const fromIndex = event.detail.from;
const toIndex = event.detail.to;
const items = [...this.items]; // sua estrutura visual
const formArray = this.formGroup as FormArray; // FormArray reativo
if (fromIndex !== toIndex) {
// Reordenar os dados visuais
const itemToMove = items.splice(fromIndex, 1)[0];
items.splice(toIndex, 0, itemToMove);
items.forEach((item, index) => item['index'] = index + 1);
// Reordenar os controles do FormArray
const controlToMove = formArray.at(fromIndex);
formArray.removeAt(fromIndex);
formArray.insert(toIndex, controlToMove);
}
// Finaliza a operação de reorder do Ionic
event.detail.complete();
}
/**
* @description Handles accordion state change events from user interactions.
* @summary Processes CustomEvent objects triggered when users expand or collapse the accordion.
* This method extracts the new state from the event details and updates the component's
* internal state accordingly. It specifically listens for ION-ACCORDION-GROUP events to
* ensure proper event source validation and prevent handling of unrelated events.
*
* @param {CustomEvent} event - The event object containing accordion state change details
* @returns {void}
*
* @mermaid
* sequenceDiagram
* participant U as User
* participant I as Ion-Accordion
* participant F as FieldsetComponent
*
* U->>I: Click accordion header
* I->>F: handleChange(CustomEvent)
* F->>F: Extract target and detail from event
* F->>F: Validate target is ION-ACCORDION-GROUP
* alt valid target
* F->>F: Update isOpen = !!value
* end
* F->>I: Reflect updated state
*
* @memberOf FieldsetComponent
*/
/**
* @description Handles accordion toggle functionality with validation error consideration.
* @summary Manages the expand/collapse state of the accordion while respecting validation error states.
* When validation errors are present, the accordion cannot be collapsed to ensure users can see
* and address the errors. When no errors exist, users can freely toggle the accordion state.
* This method also stops event propagation to prevent unwanted side effects.
*
* @param {CustomEvent} [event] - Optional event object from user interaction
* @returns {void}
* @memberOf FieldsetComponent
*/
handleAccordionToggle(event?: CustomEvent): void {
if(event)
event.stopImmediatePropagation();
if(!this.hasValidationErrors) {
this.accordionComponent.value = this.isOpen ? undefined : 'open';
this.isOpen = !!this.accordionComponent.value;
}
}
/**
* @description Handles validation error events from child form fields.
* @summary Processes validation error events dispatched by form fields within the fieldset.
* When errors are detected, the accordion is forced open and prevented from collapsing
* to ensure users can see the validation messages. This method updates the component's
* error state and accordion visibility accordingly.
*
* @param {CustomEvent} event - Custom event containing validation error details
* @returns {void}
* @memberOf FieldsetComponent
*/
handleValidationError(event: CustomEvent): void {
event.stopImmediatePropagation();
const {hasErrors} = event.detail;
this.isOpen = this.hasValidationErrors = hasErrors;
if(hasErrors)
this.accordionComponent.value = 'open';
}
/**
* @description Processes and stores a new or updated value in the fieldset.
* @summary Handles both create and update operations for fieldset items. Parses and cleans
* the input value, determines the operation type based on the updating state, and either
* adds a new item or updates an existing one. Maintains data integrity and UI consistency.
*
* @returns {void}
* @private
* @memberOf FieldsetComponent
*/
private setValue(): void {
this.value = (this.formGroup as FormArray).controls.map(({value}) => value);
this.items = this.value
.filter(v => v[this.pk] !== undefined)
.map((v, index) => {
return {
...itemMapper(v, this.mapper),
index: index + 1
} as IFieldSetItem;
});
const inputContainers = this.component.nativeElement.querySelectorAll('.dcf-input-item');
inputContainers.forEach((container: HTMLElement) => {
const input = container.querySelector('input, ion-input, ion-textarea, textarea') as HTMLInputElement | null;
if(input)
input.value = '';
})
this.updatingItem = undefined;
}
/**
* @description Automatically configures the field mapping based on the value structure.
* @summary Analyzes the provided value object to automatically determine the primary key
* and create appropriate field mappings for display purposes. Sets up the mapper object
* with title, description, and index fields based on the available data structure.
*
* @param {KeyValue} value - Sample value object used to determine field mappings
* @returns {KeyValue} The configured mapper object
* @private
* @memberOf FieldsetComponent
*/
private getMapper(value: KeyValue): KeyValue {
if(!this.pk)
this.pk = Object.keys(value)[0];
if(!Object.keys(this.mapper).length)
this.mapper['title'] = this.pk;
this.mapper['index'] = "index";
for(const key in value) {
if(Object.keys(this.mapper).length >= 2 || Object.keys(this.mapper).length === Object.keys(value).length)
break;
if(!this.mapper['title']) {
this.mapper['title'] = key;
} else {
this.mapper['description'] = key;
}
}
return this.mapper;
}
}
Source