Modal Using Portal

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Creating Modals Using UX Portal System

- Index: 3 -

Overview

This guide demonstrates how to create functional modals using the UX Portal system instead of traditional modal services. The UX Portal approach provides better performance, more flexibility, and easier integration with Angular applications.

🎯 Why Use UX Portal for Modals?

Advantages Over Traditional Modal Services

Common Modal Service Issues

🚀 Implementation Guide

Step 1: Import UX Portal Dependencies

import { UxPortalService } from 'libs/ux-portal/src/lib/services/ux-portal.service';
import { UxPortalConfig } from 'libs/ux-portal/src/lib/models/ux-portal.models';

Step 2: Inject UX Portal Service

@Component({
  selector: 'your-component',
  standalone: true,
  // ... other config
})
export class YourComponent {
  private readonly portalService = inject(UxPortalService);
  
  // ... rest of component
}

Step 3: Create Modal HTML Generator

private getModalHTML(): string {
  return `
    <div class="modal-container" style="
      min-width: 500px;
      max-width: 600px;
      background: var(--theme-surface, #ffffff);
      border-radius: 16px;
      padding: 0;
      box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
      overflow: hidden;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    ">
      <!-- Modal Header -->
      <div class="modal-header" style="
        padding: 2rem 2rem 1rem 2rem;
        border-bottom: 1px solid var(--theme-border, #e5e7eb);
        background: var(--theme-surface-elevated, #f8fafc);
      ">
        <div style="display: flex; align-items: center; justify-content: space-between;">
          <h2 style="margin: 0; font-size: 1.5rem; font-weight: 600; color: var(--theme-text-primary, #1f2937);">
            🎯 Modal Title
          </h2>
          <button onclick="closeModal()" style="
            background: transparent;
            border: none;
            font-size: 1.5rem;
            color: var(--theme-text-secondary, #6b7280);
            cursor: pointer;
            padding: 0.5rem;
            border-radius: 8px;
          ">✕</button>
        </div>
      </div>

      <!-- Modal Content -->
      <div class="modal-content" style="padding: 2rem;">
        <!-- Your form fields here -->
        <div style="margin-bottom: 1.5rem;">
          <label style="display: block; margin-bottom: 0.5rem; font-weight: 500;">Field Label</label>
          <input 
            type="text"
            id="modal-input"
            placeholder="Enter value"
            style="
              width: 100%;
              padding: 0.75rem;
              border: 1px solid var(--theme-border, #d1d5db);
              border-radius: 8px;
              font-size: 0.875rem;
              box-sizing: border-box;
            "
            oninput="validateForm()"
          />
          <div id="error-message" style="
            margin-top: 0.5rem;
            color: var(--theme-error, #dc2626);
            font-size: 0.75rem;
            display: none;
          "></div>
        </div>
      </div>

      <!-- Modal Actions -->
      <div class="modal-actions" style="
        padding: 1.5rem 2rem 2rem 2rem;
        border-top: 1px solid var(--theme-border, #e5e7eb);
        background: var(--theme-surface-elevated, #f8fafc);
        display: flex;
        gap: 1rem;
      ">
        <button 
          id="submit-button"
          onclick="submitForm()"
          disabled
          style="
            flex: 1;
            padding: 0.75rem 1.5rem;
            background: var(--theme-primary, #6366f1);
            color: white;
            border: none;
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            opacity: 0.5;
          "
        >
          Submit
        </button>
        <button onclick="closeModal()" style="
          padding: 0.75rem 1.5rem;
          background: transparent;
          color: var(--theme-text-secondary, #6b7280);
          border: 1px solid var(--theme-border, #d1d5db);
          border-radius: 8px;
          font-weight: 500;
          cursor: pointer;
        ">
          Cancel
        </button>
      </div>
    </div>

    <script>
      console.log('📜 Modal script loaded');
      
      function validateForm() {
        const input = document.getElementById('modal-input');
        const errorDiv = document.getElementById('error-message');
        const submitButton = document.getElementById('submit-button');
        
        if (!input || !errorDiv || !submitButton) {
          console.error('❌ Required elements not found');
          return false;
        }
        
        const value = input.value.trim();
        
        if (!value || value.length < 2) {
          errorDiv.textContent = value ? 'Must be at least 2 characters' : 'This field is required';
          errorDiv.style.display = 'block';
          submitButton.disabled = true;
          submitButton.style.opacity = '0.5';
          return false;
        }
        
        errorDiv.style.display = 'none';
        submitButton.disabled = false;
        submitButton.style.opacity = '1';
        return true;
      }
      
      function submitForm() {
        if (!validateForm()) return;
        
        const input = document.getElementById('modal-input');
        const formData = {
          value: input.value.trim()
        };
        
        // Call the Angular component method
        if (window.yourComponent) {
          window.yourComponent.handleFormSubmit(formData);
        }
        
        closeModal();
      }
      
      function closeModal() {
        if (window.uxPortalService) {
          window.uxPortalService.closePortal('your-modal-id');
        }
      }
      
      // Initialize form validation
      setTimeout(() => {
        const input = document.getElementById('modal-input');
        if (input) {
          input.addEventListener('input', validateForm);
          input.addEventListener('keyup', validateForm);
          input.addEventListener('paste', () => setTimeout(validateForm, 10));
          validateForm(); // Initial validation
        }
      }, 100);
    </script>
  `;
}

Step 4: Open Modal Method

async openModal(): Promise<void> {
  try {
    console.log('🎯 Opening modal');
    
    const modalHTML = this.getModalHTML();
    const portalConfig: UxPortalConfig = {
      id: 'your-modal-id',
      content: {
        type: 'string',
        data: modalHTML
      },
      backdrop: {
        type: 'modal',
        enabled: true,
        color: 'rgba(0, 0, 0, 0.5)',
        opacity: 1,
        blur: 8,
        clickToClose: false
      },
      container: {
        mode: 'modal',
        closeOnOutsideClick: false,
        closeOnEscape: true,
        constrainToViewport: true,
        allowOverflow: false,
        trapFocus: false,
        restoreFocus: true,
        autoFocus: false
      },
      positioning: {
        strategy: 'center'
      }
    };

    const result = this.portalService.openPortal(portalConfig);
    
    if (result.success) {
      console.log('✅ Modal opened successfully:', result.portalId);
      this.setupModalHandlers();
    } else {
      console.error('❌ Failed to open modal:', result.error);
    }
  } catch (error: any) {
    console.error('Failed to open modal:', error);
  }
}

Step 5: Setup Global Handlers

private setupModalHandlers(): void {
  // Make the component and portal service available to the modal's JavaScript
  (window as any).yourComponent = this;
  (window as any).uxPortalService = this.portalService;
}

// Handle form submission
handleFormSubmit(data: { value: string }): void {
  console.log('📊 Form submitted:', data);
  // Process the form data here
}

🚨 Common Issues and Solutions

Issue 1: Modal Appears Empty

❌ Problem: Modal opens but shows no content

✅ Solution:

// ❌ DON'T DO THIS - External file won't load
const response = await fetch('/path/to/modal.html');

// ✅ DO THIS - Inline HTML string
private getModalHTML(): string {
  return `<div>Your HTML content here</div>`;
}

Issue 2: Form Validation Not Working

❌ Problem: Submit button never enables, validation doesn't trigger

✅ Solution:

// ✅ Proper validation setup
setTimeout(() => {
  const input = document.getElementById('modal-input');
  if (input) {
    input.addEventListener('input', validateForm);
    input.addEventListener('keyup', validateForm);
    input.addEventListener('paste', () => setTimeout(validateForm, 10));
    validateForm(); // Initial validation check
  }
}, 100);

Issue 3: JavaScript Functions Not Found

❌ Problem: onclick handlers throw "function not defined" errors like validateDatabaseName is not defined

✅ Solution:

// ❌ DON'T DO THIS - Functions defined in embedded script (timing issues)
const html = `
  <input oninput="validateForm()" />
  <script>
    function validateForm() { /* ... */ }
  </script>
`;

// ✅ DO THIS - Define functions globally FIRST
private setupModalHandlers(): void {
  // Define functions globally BEFORE opening portal
  (window as any).validateForm = () => {
    // Your validation logic here
  };
  
  (window as any).submitForm = () => {
    // Your submit logic here
  };
  
  (window as any).closeModal = () => {
    this.portalService.closePortal('modal-id');
  };
}

async openModal(): Promise<void> {
  // Setup handlers FIRST
  this.setupModalHandlers();
  
  // Then create and open portal
  const html = `<input oninput="validateForm()" />`;
  // ... rest of portal config
}

🚨 Critical Timing Issue: HTML elements with onclick or oninput handlers are processed immediately when the portal renders, but embedded <script> tags are processed later. This creates a race condition where the handlers try to call functions that don't exist yet.

Issue 4: Modal Service Errors

❌ Problem: "Modal component not set. Call setModalComponent first."

✅ Solution: Use UX Portal instead of Modal Service

// ❌ DON'T USE - Traditional modal service
this.modalService.openComponent({...});

// ✅ USE THIS - UX Portal system
this.portalService.openPortal({...});

📋 Best Practices Checklist

✅ Do This:

❌ Don't Do This:

🎯 Real-World Example: Database Creation Modal

See the Storage Inspector's database creation modal for a complete working example:

// File: apps/ux-test/src/app/storage/inspector/storage-inspector.component.ts
// Method: createNewDatabase()
// HTML Generator: getDatabaseCreationModalHTML()

This implementation demonstrates:

🚀 Advanced Features

Dynamic Content Updates

function updateContent(newData) {
  const element = document.getElementById('dynamic-content');
  if (element) {
    element.innerHTML = newData;
  }
}

Multi-Step Forms

let currentStep = 1;
const totalSteps = 3;

function nextStep() {
  if (currentStep < totalSteps) {
    document.getElementById(`step-${currentStep}`).style.display = 'none';
    currentStep++;
    document.getElementById(`step-${currentStep}`).style.display = 'block';
  }
}

Custom Animations

const portalConfig: UxPortalConfig = {
  // ... other config
  animation: {
    entrance: 'scale',
    exit: 'fade',
    duration: 300,
    enabled: true
  }
};

🎉 Summary

The UX Portal system provides a superior alternative to traditional modal services with:

Follow this guide to create professional, functional modals that integrate seamlessly with your Angular application!