UNPKG

df-ae-forms-package

Version:

A comprehensive React form preview component library with form controls, validation, and responsive design for Angular/Ionic integration

784 lines (648 loc) 20.8 kB
# DF AE Forms Package - Integration Guide for Ionic/Angular This guide provides step-by-step instructions for integrating the `df-ae-forms-package` into your Ionic/Angular application. ## Table of Contents 1. [Prerequisites](#prerequisites) 2. [Installation](#installation) 3. [Basic Setup](#basic-setup) 4. [React Bridge Setup](#react-bridge-setup) 5. [Using the Component](#using-the-component) 6. [Styling](#styling) 7. [Form Data Handling](#form-data-handling) 8. [Common Issues and Solutions](#common-issues-and-solutions) 9. [Complete Example](#complete-example) ## Prerequisites - Angular 12+ application - Ionic 6+ application - Node.js 14+ and npm 6+ ## Installation ### 1. Install the Package ```bash npm install df-ae-forms-package ``` ### 2. Install Peer Dependencies ```bash npm install react react-dom lucide-react @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities ``` ### 3. Install React Bridge for Angular ```bash npm install @angular-react/react ``` ## Basic Setup ### 1. Import Styles In your `src/global.scss` or `src/styles.scss` file, import the package styles: ```scss // Import DF Forms Package styles @import '~df-ae-forms-package/dist/index.css'; ``` **Important**: The CSS file contains all the design system variables and component styles. Make sure this import is at the top of your global styles file. ### 2. Ensure CSS Variables are Available The package uses CSS variables for theming. These are included in the CSS file, but you can override them in your application: ```scss :root { // Override package variables if needed --df-color-primary: #303992; --df-color-secondary: #ff7032; --df-color-error-primary: #f04248; // ... other variables } ``` ## React Bridge Setup Since this is a React component and you're using Angular, you need to set up a React bridge. ### 1. Create a React Wrapper Component Create a new file `src/app/components/react-form-wrapper.tsx`: ```tsx import React from 'react'; import { DfFormPreview, FormComponentType, DeviceType } from 'df-ae-forms-package'; interface ReactFormWrapperProps { formComponents: FormComponentType[]; currentDevice?: DeviceType; isPreviewMode?: boolean; initialFormData?: FormComponentType[]; formTitle?: string; formDescription?: string; formTemplateId?: string; onSubmit?: (formData: FormComponentType[]) => void; onFormDataChange?: (formData: FormComponentType[]) => void; } export const ReactFormWrapper: React.FC<ReactFormWrapperProps> = (props) => { return <DfFormPreview {...props} />; }; ``` ### 2. Create Angular Component Wrapper Create `src/app/components/form-preview.component.ts`: ```typescript import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import { createRoot, Root } from 'react-dom/client'; import * as React from 'react'; import { ReactFormWrapper } from './react-form-wrapper'; import { FormComponentType, DeviceType } from 'df-ae-forms-package'; @Component({ selector: 'app-form-preview', template: '<div #reactContainer></div>', styles: [` :host { display: block; width: 100%; height: 100%; } `] }) export class FormPreviewComponent implements OnInit, OnDestroy { @ViewChild('reactContainer', { static: true }) containerRef!: ElementRef; @Input() formComponents: FormComponentType[] = []; @Input() currentDevice: DeviceType = 'desktop'; @Input() isPreviewMode: boolean = false; @Input() initialFormData: FormComponentType[] = []; @Input() formTitle?: string; @Input() formDescription?: string; @Input() formTemplateId?: string; @Output() formSubmit = new EventEmitter<FormComponentType[]>(); @Output() formDataChange = new EventEmitter<FormComponentType[]>(); private root: Root | null = null; ngOnInit() { this.renderReactComponent(); } ngOnDestroy() { if (this.root) { this.root.unmount(); } } private renderReactComponent() { if (!this.root) { this.root = createRoot(this.containerRef.nativeElement); } this.root.render( React.createElement(ReactFormWrapper, { formComponents: this.formComponents, currentDevice: this.currentDevice, isPreviewMode: this.isPreviewMode, initialFormData: this.initialFormData, formTitle: this.formTitle, formDescription: this.formDescription, formTemplateId: this.formTemplateId, onSubmit: (formData: FormComponentType[]) => { this.formSubmit.emit(formData); }, onFormDataChange: (formData: FormComponentType[]) => { this.formDataChange.emit(formData); } }) ); } ngOnChanges() { if (this.root) { this.renderReactComponent(); } } } ``` ### 3. Alternative: Using @angular-react/react If you prefer using the `@angular-react/react` package: ```typescript import { Component, Input, Output, EventEmitter } from '@angular/core'; import { ReactComponent } from '@angular-react/react'; import { DfFormPreview, FormComponentType, DeviceType } from 'df-ae-forms-package'; @Component({ selector: 'app-form-preview', template: ` <react-component [component]="DfFormPreview" [props]="formProps" ></react-component> ` }) export class FormPreviewComponent { DfFormPreview = DfFormPreview; @Input() formComponents: FormComponentType[] = []; @Input() currentDevice: DeviceType = 'desktop'; @Input() isPreviewMode: boolean = false; // ... other inputs @Output() formSubmit = new EventEmitter<FormComponentType[]>(); @Output() formDataChange = new EventEmitter<FormComponentType[]>(); get formProps() { return { formComponents: this.formComponents, currentDevice: this.currentDevice, isPreviewMode: this.isPreviewMode, onSubmit: (formData: FormComponentType[]) => this.formSubmit.emit(formData), onFormDataChange: (formData: FormComponentType[]) => this.formDataChange.emit(formData) }; } } ``` ## Using the Component ### 1. In Your Angular Component ```typescript import { Component, OnInit } from '@angular/core'; import { FormComponentType } from 'df-ae-forms-package'; import { FormService } from './services/form.service'; // Your service @Component({ selector: 'app-form-list', template: ` <ion-content> <ion-list> <ion-item *ngFor="let form of forms" (click)="openForm(form)"> <ion-label>{{ form.title }}</ion-label> </ion-item> </ion-list> </ion-content> ` }) export class FormListPage implements OnInit { forms: any[] = []; constructor(private formService: FormService) {} ngOnInit() { this.loadForms(); } loadForms() { this.formService.getForms().subscribe(forms => { this.forms = forms; }); } openForm(form: any) { // Navigate to form preview page this.router.navigate(['/form-preview', form.id]); } } ``` ### 2. Form Preview Page ```typescript import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { FormComponentType } from 'df-ae-forms-package'; import { FormService } from './services/form.service'; @Component({ selector: 'app-form-preview', template: ` <ion-header> <ion-toolbar> <ion-title>{{ formTitle }}</ion-title> </ion-toolbar> </ion-header> <ion-content> <app-form-preview [formComponents]="formComponents" [currentDevice]="currentDevice" [isPreviewMode]="false" [formTitle]="formTitle" [formDescription]="formDescription" [formTemplateId]="formTemplateId" (formSubmit)="handleFormSubmit($event)" (formDataChange)="handleFormDataChange($event)" ></app-form-preview> </ion-content> ` }) export class FormPreviewPage implements OnInit { formComponents: FormComponentType[] = []; formTitle: string = ''; formDescription: string = ''; formTemplateId: string = ''; currentDevice: 'desktop' | 'tablet' | 'mobile' = 'desktop'; constructor( private route: ActivatedRoute, private formService: FormService ) {} ngOnInit() { const formId = this.route.snapshot.paramMap.get('id'); if (formId) { this.loadForm(formId); } } loadForm(formId: string) { this.formService.getFormById(formId).subscribe(form => { // Assuming your API returns form data in the correct format this.formComponents = form.components || []; this.formTitle = form.title || ''; this.formDescription = form.description || ''; this.formTemplateId = form.id || ''; }); } handleFormSubmit(formData: FormComponentType[]) { // Handle form submission this.formService.submitForm(this.formTemplateId, formData).subscribe( response => { console.log('Form submitted successfully', response); // Show success message, navigate, etc. }, error => { console.error('Form submission failed', error); // Show error message } ); } handleFormDataChange(formData: FormComponentType[]) { // Handle form data changes (for auto-save, etc.) console.log('Form data changed', formData); } } ``` ## Styling ### 1. Ensure Styles are Loaded Make sure the CSS is imported in your global styles: ```scss // src/global.scss @import '~df-ae-forms-package/dist/index.css'; ``` ### 2. Override CSS Variables (Optional) ```scss :root { --df-color-primary: #303992; --df-color-secondary: #ff7032; --df-color-error-primary: #f04248; --df-color-text-dark: #000000; --df-color-text-light: #8c8c8c; --df-color-fb-container: #ffffff; --df-color-fb-border: #e4e5ec; --df-color-fb-bg: #f4f4f4; } ``` ### 3. Dark Mode Support The package supports dark mode. Add the `dark` class to your root element: ```typescript // In your app.component.ts export class AppComponent { isDarkMode = false; toggleDarkMode() { this.isDarkMode = !this.isDarkMode; document.body.classList.toggle('dark', this.isDarkMode); } } ``` ## Form Data Handling ### 1. Loading Form Data from API ```typescript // form.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { FormComponentType } from 'df-ae-forms-package'; @Injectable({ providedIn: 'root' }) export class FormService { private apiUrl = 'https://your-api.com/api'; constructor(private http: HttpClient) {} getForms(): Observable<any[]> { return this.http.get<any[]>(`${this.apiUrl}/forms`); } getFormById(id: string): Observable<any> { return this.http.get<any>(`${this.apiUrl}/forms/${id}`); } submitForm(formId: string, formData: FormComponentType[]): Observable<any> { return this.http.post(`${this.apiUrl}/forms/${formId}/submit`, { components: formData }); } } ``` ### 2. Form Data Format The form components expect data in this format: ```typescript const formComponents: FormComponentType[] = [ { id: 'field-1', name: 'text-input', basic: { label: 'Full Name', placeholder: 'Enter your full name', defaultValue: '' }, validation: { required: true, minLength: 2, maxLength: 50 }, styles: { column: 12, labelAlignment: 'top' } }, { id: 'field-2', name: 'email-input', basic: { label: 'Email Address', placeholder: 'Enter your email', defaultValue: '' }, validation: { required: true } } ]; ``` ## Common Issues and Solutions ### Issue 1: Styles Not Working **Problem**: Form components are not styled correctly. **Solutions**: 1. Ensure CSS is imported: `@import '~df-ae-forms-package/dist/index.css';` 2. Check that CSS variables are defined 3. Verify the build includes the CSS file 4. Clear browser cache and rebuild ### Issue 2: Labels Not Showing **Problem**: Form labels are not visible. **Solutions**: 1. Verify that `formComponents[].basic.label` is set for each component 2. Check that `hideLabel` prop is not set to `true` 3. Ensure CSS is properly loaded 4. Check browser console for errors ### Issue 3: Components Not Rendering **Problem**: Form components are not appearing. **Solutions**: 1. Verify `formComponents` array is not empty 2. Check that component IDs are unique 3. Ensure conditional logic is not hiding components 4. Check browser console for React errors 5. Verify React bridge is properly set up ### Issue 4: Validation Not Working **Problem**: Form validation is not functioning. **Solutions**: 1. Ensure `isPreviewMode` is set to `false` for test mode 2. Check that validation rules are properly defined 3. Verify `onFormDataChange` callback is working 4. Check that form submission handler is properly connected ### Issue 5: React Bridge Issues **Problem**: React components are not rendering in Angular. **Solutions**: 1. Verify `react` and `react-dom` are installed 2. Check that React bridge is properly configured 3. Ensure `createRoot` is used (React 18+) 4. Check for version conflicts between React and Angular ## Complete Example Here's a complete working example: ### 1. Module Setup ```typescript // app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { FormListPage } from './pages/form-list/form-list.page'; import { FormPreviewPage } from './pages/form-preview/form-preview.page'; import { FormPreviewComponent } from './components/form-preview.component'; @NgModule({ declarations: [ AppComponent, FormListPage, FormPreviewPage, FormPreviewComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule, IonicModule.forRoot() ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} ``` ### 2. Service ```typescript // services/form.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { FormComponentType } from 'df-ae-forms-package'; @Injectable({ providedIn: 'root' }) export class FormService { private apiUrl = 'https://your-api.com/api'; constructor(private http: HttpClient) {} getForms(): Observable<any[]> { return this.http.get<any[]>(`${this.apiUrl}/forms`); } getFormById(id: string): Observable<{ components: FormComponentType[]; title: string; description: string; id: string; }> { return this.http.get<any>(`${this.apiUrl}/forms/${id}`).pipe( map(response => ({ components: response.components || [], title: response.title || '', description: response.description || '', id: response.id || '' })) ); } submitForm(formId: string, formData: FormComponentType[]): Observable<any> { return this.http.post(`${this.apiUrl}/forms/${formId}/submit`, { components: formData, submittedAt: new Date().toISOString() }); } } ``` ### 3. Form List Page ```typescript // pages/form-list/form-list.page.ts import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { FormService } from '../../services/form.service'; @Component({ selector: 'app-form-list', templateUrl: './form-list.page.html', styleUrls: ['./form-list.page.scss'] }) export class FormListPage implements OnInit { forms: any[] = []; loading = false; constructor( private formService: FormService, private router: Router ) {} ngOnInit() { this.loadForms(); } loadForms() { this.loading = true; this.formService.getForms().subscribe( forms => { this.forms = forms; this.loading = false; }, error => { console.error('Error loading forms', error); this.loading = false; } ); } openForm(form: any) { this.router.navigate(['/form-preview', form.id]); } } ``` ### 4. Form Preview Page ```typescript // pages/form-preview/form-preview.page.ts import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { FormComponentType } from 'df-ae-forms-package'; import { FormService } from '../../services/form.service'; import { ToastController } from '@ionic/angular'; @Component({ selector: 'app-form-preview', templateUrl: './form-preview.page.html', styleUrls: ['./form-preview.page.scss'] }) export class FormPreviewPage implements OnInit { formComponents: FormComponentType[] = []; formTitle: string = ''; formDescription: string = ''; formTemplateId: string = ''; currentDevice: 'desktop' | 'tablet' | 'mobile' = 'desktop'; loading = false; constructor( private route: ActivatedRoute, private router: Router, private formService: FormService, private toastController: ToastController ) {} ngOnInit() { const formId = this.route.snapshot.paramMap.get('id'); if (formId) { this.loadForm(formId); } } loadForm(formId: string) { this.loading = true; this.formService.getFormById(formId).subscribe( form => { this.formComponents = form.components; this.formTitle = form.title; this.formDescription = form.description; this.formTemplateId = form.id; this.loading = false; }, error => { console.error('Error loading form', error); this.loading = false; this.showErrorToast('Failed to load form'); } ); } handleFormSubmit(formData: FormComponentType[]) { this.loading = true; this.formService.submitForm(this.formTemplateId, formData).subscribe( response => { this.loading = false; this.showSuccessToast('Form submitted successfully'); // Navigate back or to success page setTimeout(() => { this.router.navigate(['/form-list']); }, 2000); }, error => { this.loading = false; this.showErrorToast('Failed to submit form'); } ); } handleFormDataChange(formData: FormComponentType[]) { // Optional: Auto-save functionality console.log('Form data changed', formData); } async showSuccessToast(message: string) { const toast = await this.toastController.create({ message, duration: 2000, color: 'success', position: 'top' }); toast.present(); } async showErrorToast(message: string) { const toast = await this.toastController.create({ message, duration: 3000, color: 'danger', position: 'top' }); toast.present(); } } ``` ### 5. Template ```html <!-- pages/form-preview/form-preview.page.html --> <ion-header> <ion-toolbar> <ion-buttons slot="start"> <ion-back-button></ion-back-button> </ion-buttons> <ion-title>{{ formTitle || 'Form Preview' }}</ion-title> </ion-toolbar> </ion-header> <ion-content> <div *ngIf="loading" class="loading-container"> <ion-spinner></ion-spinner> </div> <app-form-preview *ngIf="!loading && formComponents.length > 0" [formComponents]="formComponents" [currentDevice]="currentDevice" [isPreviewMode]="false" [formTitle]="formTitle" [formDescription]="formDescription" [formTemplateId]="formTemplateId" (formSubmit)="handleFormSubmit($event)" (formDataChange)="handleFormDataChange($event)" ></app-form-preview> </ion-content> ``` ## Summary 1. **Install** the package and peer dependencies 2. **Import** the CSS in your global styles 3. **Set up** React bridge for Angular 4. **Create** wrapper components 5. **Load** form data from your API 6. **Use** the component in your pages 7. **Handle** form submission and data changes The package is now ready to use in your Ionic/Angular application!