Source

app/components/back-button/back-button.component.ts

import { OnInit, CUSTOM_ELEMENTS_SCHEMA, Input, Component, inject, HostListener, ViewChild, ElementRef } from "@angular/core";
import { PredefinedColors } from "@ionic/core";
import { ForAngularCommonModule } from 'src/lib/for-angular-common.module';
import { ComponentEventNames, RouteDirections } from "src/lib/engine/constants";
import { StringOrBoolean } from "src/lib/engine/types";
import { stringToBoolean } from "src/lib/utils/helpers";
import { Location } from '@angular/common';
import { windowEventEmitter } from "src/lib/utils/helpers";
import { addIcons } from 'ionicons';
import { chevronBackOutline } from 'ionicons/icons';
import { FunctionLike } from "src/lib/engine/types";
import { NgxMediaService } from "src/lib/services/NgxMediaService";

/**
 * @description Back button component for navigation within Angular applications.
 * @summary The BackButtonComponent provides a standardized back navigation button with
 * customizable appearance and behavior. It supports both traditional browser back navigation
 * and custom navigation logic through function callbacks. The component integrates with
 * Angular's Location service for browser history management and provides event emission
 * capabilities for external components to react to navigation actions.
 *
 * Key features include:
 * - Customizable appearance with Ionic color schemes
 * - Prevention of default navigation behavior for custom handling
 * - Event emission for external component integration
 * - Support for custom navigation targets (URLs or functions)
 * - Configurable text display alongside the icon
 * - Integration with browser history API
 *
 * @component BackButtonComponent
 * @example
 * ```html
 * <!-- Basic back button -->
 * <app-back-button></app-back-button>
 *
 * <!-- Custom colored back button with text -->
 * <app-back-button
 *   color="secondary"
 *   showText="true"
 *   text="Go Back">
 * </app-back-button>
 *
 * <!-- Back button with custom navigation -->
 * <app-back-button
 *   [link]="customBackFunction"
 *   preventDefault="true">
 * </app-back-button>
 *
 * <!-- Back button that navigates to specific URL -->
 * <app-back-button
 *   link="/dashboard"
 *   direction="forward">
 * </app-back-button>
 * ```
 *
 * @mermaid
 * sequenceDiagram
 *   participant U as User
 *   participant BBC as BackButtonComponent
 *   participant L as Location Service
 *   participant E as Event System
 *   participant C as Custom Function
 *
 *   U->>BBC: Click back button
 *   BBC->>BBC: backToPage()
 *
 *   alt preventDefault is true
 *     BBC->>BBC: handleEndNavigation()
 *     BBC->>E: Emit navigation event
 *     Note over BBC: Navigation prevented
 *   else preventDefault is false
 *     BBC->>BBC: handleEndNavigation()
 *     BBC->>E: Emit navigation event
 *
 *     alt No custom link
 *       BBC->>L: location.back()
 *       L->>L: Navigate to previous page
 *     else Custom function provided
 *       BBC->>C: Execute custom function
 *       C->>C: Handle custom navigation
 *     else URL string provided
 *       Note over BBC: Navigate to URL (implementation dependent)
 *     end
 *   end
 *
 * @memberOf app.components
 */
@Component({
  selector: 'app-back-button',
  templateUrl: './back-button.component.html',
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  styleUrls: ['./back-button.component.scss'],
  imports: [ForAngularCommonModule],
  standalone: true,

})
export class BackButtonComponent implements OnInit {

    /**
   * @description Reference to the component's native DOM element.
   * @summary Provides direct access to the native DOM element of the component through Angular's
   * ViewChild decorator. This reference can be used to manipulate the DOM element directly,
   * apply custom styles, or access native element properties and methods. The element is
   * identified by the 'component' template reference variable.
   * @type {ElementRef}
   */
  @ViewChild('component', { read: ElementRef, static: true })
  component!: ElementRef;


  /**
   * @description Flag to enable or disable dark mode support for the component.
   * @summary When enabled, the component will automatically detect the system's dark mode
   * preference using the media service and apply appropriate styling classes. This flag
   * controls whether the component should respond to dark mode changes and apply the
   * dark palette class to its DOM element. By default, dark mode support is disabled.
   * @protected
   * @type {boolean}
   * @default false
   */
  @Input()
  protected isDarkMode: boolean = false;



  /**
   * @description Controls whether default navigation behavior is prevented.
   * @summary When set to true, clicking the back button will only emit events without
   * performing actual navigation. This is useful for cases where you want to intercept
   * the back action and handle it in a custom way, such as showing a confirmation dialog
   * before navigating away from a form with unsaved changes.
   *
   * @type {StringOrBoolean}
   * @default false
   * @memberOf BackButtonComponent
   */
  @Input()
  preventDefault: StringOrBoolean = false;

  /**
   * @description Controls whether navigation events are emitted.
   * @summary When set to true, the component will emit window events when navigation
   * occurs. These events can be listened to by other components to react to navigation
   * changes. For example, a component might listen for back navigation events to reset
   * its state or update its display.
   *
   * @type {StringOrBoolean}
   * @default true
   * @memberOf BackButtonComponent
   */
  @Input()
  emitEvent: StringOrBoolean = true;

  /**
   * @description Custom navigation target for the back button.
   * @summary Specifies where the back button should navigate to when clicked.
   * This can be either a URL string or a function that handles navigation.
   * When not provided, the component will navigate to the previous URL in the browser history.
   * When a function is provided, it will be called instead of performing standard navigation.
   *
   * @type {(string | FunctionLike | undefined)}
   * @memberOf BackButtonComponent
   */
  @Input()
  link?: string | FunctionLike;

  /**
   * @description The direction of navigation animation.
   * @summary Controls the animation and transition behavior when navigating.
   * This affects how the page transition appears to the user. For example,
   * BACK typically slides the page from left to right, while FORWARD slides
   * from right to left. This helps provide visual cues about navigation direction.
   *
   * @type {RouteDirections}
   * @default RouteDirections.BACK
   * @memberOf BackButtonComponent
   */
  @Input()
  direction: RouteDirections = RouteDirections.BACK;

  /**
   * @description Determines if text should be displayed alongside the icon.
   * @summary When set to true, the component will display text next to the icon.
   * This text can be customized using the text property. Displaying text can
   * make the button's purpose clearer to users, especially for accessibility.
   *
   * @type {StringOrBoolean}
   * @default false
   * @memberOf BackButtonComponent
   */
  @Input()
  showText: StringOrBoolean = false;

  /**
   * @description Text to display when showText is true.
   * @summary Specifies the text content to show next to the icon when showText is enabled.
   * This can be a direct string or a translation key. By default, it shows "back".
   * The text can be localized based on the component's locale settings.
   *
   * @type {string}
   * @default 'back'
   * @memberOf BackButtonComponent
   */
  @Input()
  text?: string = 'back';

  /**
   * @description Color of the back button.
   * @summary Sets the color of the back button using Ionic's predefined color palette.
   * This allows the button to match the application's color scheme. Common values
   * include 'primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', etc.
   *
   * @type {PredefinedColors}
   * @default "primary"
   * @memberOf BackButtonComponent
   */
  @Input()
  color: PredefinedColors = "primary";

  /**
   * @description Color of the toolbar containing the back button.
   * @summary When set, this property changes the button color to 'light' for better contrast
   * against colored toolbars. This is useful when placing the back button in a colored
   * header or toolbar, ensuring it remains visible and accessible.
   *
   * @type {string}
   * @memberOf BackButtonComponent
   */
  @Input()
  toolbarColor?: string | PredefinedColors;

  /**
   * @description Flag indicating if the icon is an Ionic icon.
   * @summary This property is determined automatically based on the icon input.
   * It's set to false for Tabler icons (prefixed with 'ti-') or SVG paths
   * (containing '.svg'). This affects how the icon is rendered in the template.
   *
   * @type {boolean}
   * @default true
   * @memberOf BackButtonComponent
   */
  isIonIcon: boolean = true;

  /**
   * @description Angular Location service.
   * @summary Injected service that provides access to the browser's URL and history.
   * This service is used for interacting with the browser's history API, allowing
   * for back navigation and URL manipulation outside of Angular's router.
   *
   * @private
   * @type {Location}
   * @memberOf CrudFormComponent
   */
  private location: Location = inject(Location);


    /**
   * @description Angular Location service.
   * @summary Injected service that provides access to the browser's URL and history.
   * This service is used for interacting with the browser's history API, allowing
   * for back navigation and URL manipulation outside of Angular's router.
   *
   * @private
   * @type {Location}
   * @memberOf CrudFormComponent
   */
  private mediaService: NgxMediaService = inject(NgxMediaService);

  /**
   * @description Creates an instance of BackButtonComponent.
   * @summary Initializes a new BackButtonComponent
   *
   * @memberOf BackButtonComponent
   */
  constructor() {
    addIcons({chevronBackOutline})
  }

  /**
   * @description Initializes the component after Angular first displays the data-bound properties.
   * @summary Sets up the component by processing boolean inputs, determining the button color based on
   * toolbar color, retrieving the previous URL for navigation, and determining the icon type.
   * This method prepares the component for user interaction by ensuring all properties are
   * properly initialized.
   *
   * @mermaid
   * sequenceDiagram
   *   participant A as Angular Lifecycle
   *   participant B as BackButtonComponent
   *   participant R as Location
   *
   *   A->>B: ngOnInit()
   *   B->>B: Process preventDefault
   *   B->>B: Process emitEvent
   *   B->>B: Determine color based on toolbarColor
   *   B->>R: getPreviousUrl()
   *   R-->>B: Return previous URL
   *   B->>B: Store previousUrl
   *   B->>B: Process showText
   *   Note over B: Check icon type
   *   B->>B: Set isIonIcon based on icon string
   *
   * @memberOf BackButtonComponent
   */
  ngOnInit(): void {
    this.preventDefault = stringToBoolean(this.preventDefault);
    this.emitEvent = stringToBoolean(this.emitEvent);
    this.mediaService.isDarkMode().subscribe(isDark => {
      this.isDarkMode = isDark;
    });
    if(this.toolbarColor)
      this.color = this.toolbarColor as PredefinedColors;
    this.showText = stringToBoolean(this.showText);

    // if(this.showText)
    //   this.text = await this.localeService.get((!this.locale ? this.text : `${this.locale}.${this.text}`) as string);
  }

  /**
   * @description Handles navigation when the back button is clicked.
   * @summary This method is the core navigation handler for the back button. It supports three
   * navigation patterns: 1) custom function execution, 2) navigation to a specific URL, and
   * 3) default back navigation using browser history. It also handles event emission and
   * can prevent navigation when configured to do so.
   *
   * @param {boolean} forceRefresh - Whether to force a page refresh after navigation
   * @return {Promise<boolean|void>} A promise that resolves when navigation is complete
   *
   * @mermaid
   * sequenceDiagram
   *   participant U as User
   *   participant B as BackButtonComponent
   *   participant E as Event System
   *
   *   U->>B: Click back button
   *   B->>B: backToPage(forceRefresh)
   *   alt preventDefault is true
   *     B->>B: handleEndNavigation(forceRefresh)
   *     B->>E: Emit navigation event
   *     B-->>U: Return without navigation
   *   else preventDefault is false
   *     B->>B: handleEndNavigation(forceRefresh)
   *     B->>E: Emit navigation event
   *     alt link is not provided
   *       B->>R: backToLastPage()
   *     else link is a function
   *       B->>B: Execute link function
   *     else link is a URL
   *       B->>R: navigateTo(link, direction)
   *     end
   *   end
   *
   * @memberOf BackButtonComponent
   */
  async backToPage(forceRefresh: boolean = false): Promise<boolean|void> {
    if(this.preventDefault)
      return this.handleEndNavigation(forceRefresh);

    this.handleEndNavigation(forceRefresh);
    if(!this.link)
      return this.location.back();
    if(this.link instanceof Function)
      return await this.link() as void;
  }

  /**
   * @description Emits navigation events when navigation is complete.
   * @summary This method handles the emission of navigation events when the emitEvent property
   * is true. It uses the windowEventEmitter utility to broadcast a global event that other
   * components can listen for. The event includes information about whether a refresh is required.
   *
   * @param {boolean} forceRefresh - Whether to force a page refresh
   * @return {void}
   *
   * @mermaid
   * sequenceDiagram
   *   participant B as BackButtonComponent
   *   participant E as Event System
   *
   *   B->>B: handleEndNavigation(forceRefresh)
   *   alt emitEvent is true
   *     B->>E: windowEventEmitter(BACK_BUTTON_NAVIGATION, {refresh: forceRefresh})
   *   end
   *
   * @memberOf BackButtonComponent
   */
  handleEndNavigation(forceRefresh: boolean): void {
    if(this.emitEvent)
      windowEventEmitter(ComponentEventNames.BACK_BUTTON_NAVIGATION, {refresh: forceRefresh});
  }

  /**
   * @description Listens for global back button navigation events.
   * @summary This method sets up an event listener for the BackButtonForceNavigationEvent,
   * allowing external components to trigger back navigation programmatically. When this
   * event is received, the component will execute its backToPage method as if the button
   * was clicked directly.
   *
   * @param {Event} event - The event object
   * @return {Promise<boolean|void>} A promise that resolves when navigation is complete
   *
   * @mermaid
   * sequenceDiagram
   *   participant E as External Component
   *   participant W as Window
   *   participant B as BackButtonComponent
   *
   *   E->>W: Dispatch BackButtonForceNavigationEvent
   *   W->>B: handleModelPageEvent(event)
   *   B->>B: backToPage()
   *   Note over B: Execute navigation logic
   *
   * @memberOf BackButtonComponent
   */
  @HostListener('window:BackButtonForceNavigationEvent', ['$event'])
  handleModelPageEvent(): Promise<boolean|void> {
    return this.backToPage();
  }
}