Skip to content

Component Name: formInput

Last Updated: 2025-09-29 Source Code: https://bitbucket.org/i2cinc/i2c.salesforce.metadata/src/STAGING/force-app/main/default/lwc/formInput

API Name: c-formInput Type: Form Component Target: Not exposed (internal reusable component)

Business Purpose

This reusable form input component provides consistent validation, styling, and error handling across all forms in the AANP application. It supports text, email, and password input types with built-in validation patterns, required field checking, and external error message support. The component also supports multi-line textarea mode.

User Interface

Visual Description

  • Layout: Vertical label-input-error stack
  • Key UI Elements: Label (with * for required), input/textarea, error message, hint text
  • Responsive: Full-width input adapts to container

Component Structure

Files

  • formInput.html - Template
  • formInput.js - Controller (195 lines)
  • formInput.js-meta.xml - Metadata (not exposed)
  • classnames.js - Utility for CSS class management

HTML Template Structure

<template>
  <div class="form-group">
    <slot name="label">
      <label>{label}<template if:true={required}> * </template></label>
    </slot>
    <slot name="description">
      <span>{description}</span>
    </slot>

    <template lwc:if={multiline}>
      <textarea rows={rows} cols={cols} ...>{value}</textarea>
    </template>
    <template lwc:else>
      <input type={type} ... />
    </template>

    <slot name="error-message">
      <div lwc:if={hasError}>{errorMessage}</div>
      <div lwc:elseif={hasExternalError}>{externalErrorMessage}</div>
    </slot>

    <slot name="hint">
      <div>{hint}</div>
    </slot>
  </div>
</template>

Key Template Features: - Slots for label, description, error-message, hint (customizable) - Conditional textarea vs. input rendering - Dynamic CSS classes for error states - Support for all standard input attributes

JavaScript Controller

Properties (API)

@api label

  • Type: String
  • Default: 'Label'
  • Description: Input field label

@api description

  • Type: String
  • Description: Secondary help text

@api id, name, type

  • Type: String
  • Defaults: undefined, undefined, 'text'
  • Description: Standard input attributes

@api value

  • Type: String
  • Default: ''
  • Description: Input value (two-way binding via events)

@api required

  • Type: Boolean
  • Default: false
  • Description: Whether field is required

@api errorKey

  • Type: String
  • Default: 'default'
  • Description: Key for error message lookup

@api hint

  • Type: String
  • Description: Hint message below input

@api labelClass, inputClass, errorMessageClass, etc.

  • Type: String
  • Description: CSS class overrides

@api placeholder, autoComplete, pattern

  • Type: String
  • Description: Standard HTML input attributes

@api disabled

  • Type: Boolean
  • Default: false

@api min, max, minLength, maxLength

  • Type: String
  • Description: Validation attributes

@api multiline

  • Type: Boolean
  • Default: false
  • Description: Render as textarea instead of input

@api rows, cols

  • Type: String
  • Defaults: '3', '50'
  • Description: Textarea dimensions

Public Methods

@api validateInput()

validateInput() {
    const inputElement = this.multiline ?
        this.template.querySelector('textarea') :
        this.template.querySelector('input');
    this.isDirty = true;
    this.checkForError(inputElement);
    return !this.hasError && !this.hasExternalError;
}
Purpose: Programmatically validate input Returns: Boolean (true if valid) Called By: Parent components on form submit


@api setError({ message, condition })

setError({ message, condition = true }) {
    this.hasExternalError = condition;
    this.externalErrorMessage = message;
}
Purpose: Set error message from parent component Parameters: message (String), condition (Boolean) Called By: Parent components for custom validation


@api clearError()

clearError() {
    this.setError({ message: '', condition: false })
}
Purpose: Clear external error state Called By: Parent components


Event Handlers

handleChange(event)

Triggered By: Input change event Action: Sets isDirty, validates, dispatches inputchange event

handleBlur(event)

Triggered By: Input blur event Action: Validates if isDirty, dispatches inputblur event

handleInput(event)

Triggered By: Input input event (every keystroke) Action: Dispatches cinput event

handleFocus(event)

Triggered By: Input focus event Action: Dispatches focus event


Private Methods

checkForError(inputElement)

Purpose: Validates input based on type and pattern Validations: - Required field check - Email format regex - Password strength regex (8+ chars, upper, lower, number, special) - Custom pattern matching

setInternalError({ message, condition })

Purpose: Sets internal validation error state


Events

Events Dispatched

inputchange

new CustomEvent('inputchange', {
    detail: { fieldName, value }
})
When Fired: User changes input value (on blur/change) Usage: <c-form-input oninputchange={handler}>


inputblur

new CustomEvent('inputblur', {
    detail: { fieldName, value }
})
When Fired: User leaves input field


cinput

new CustomEvent('cinput', {
    detail: { fieldName, value }
})
When Fired: Every keystroke (input event)


focus

new CustomEvent('focus', {
    detail: { fieldName, value }
})
When Fired: User focuses input


Styling (CSS)

Custom CSS Classes

  • .form-group: Container
  • .error-label: Label when error (text-danger)
  • .is-invalid: Input border red
  • Computed classes use classNames utility

SLDS Classes

None

Dependencies

Apex Classes

None

Custom Utilities

  • classnames.js: CSS class concatenation utility

Configuration

Not exposed - internal component

User Interactions

Actions Available to Users

  1. Type in field: Triggers cinput on every keystroke
  2. Leave field: Triggers validation on blur (if dirty)
  3. Change value: Triggers validation and inputchange

Validation & Error Handling

Built-in Validations: - Required field - Email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - Password: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*(),.?":{}|<>]).{8,}$/ - Custom pattern via pattern attribute

Error Messages: - "Field is required" - "Please enter a valid email address" - "Password is not valid" - "Please enter a valid value" (pattern mismatch)

Error Display: - Red text below input - Red border on input - Red label text

Data Flow

Input Data Flow

Parent sets @api value
Bound to input value attribute
Displayed to user

Output Data Flow

User types
handleChange/handleInput
Validation (if needed)
Dispatch event to parent

Performance Considerations

  • Uses getters for computed CSS classes
  • Validation only runs when dirty (after first interaction)
  • Event debouncing could improve performance for cinput event

Accessibility (a11y)

ARIA Labels: - Label properly associated via for attribute - Error messages need aria-describedby link

Keyboard Navigation: - Standard input behavior - Tab order follows DOM

Screen Reader Support: - Labels announced - Error messages announced (could be improved with aria-live)

Testing

Jest Tests

None

Manual Testing

  • [ ] Required validation
  • [ ] Email validation
  • [ ] Password validation
  • [ ] Custom pattern validation
  • [ ] External error setting
  • [ ] Multiline mode
  • [ ] All props work
  • [ ] Event dispatching

Usage Examples

Basic Text Input

<c-form-input
    id="firstName"
    name="firstName"
    label="First Name"
    type="text"
    value={firstName}
    required={true}
    oninputchange={handleChange}>
</c-form-input>

Email with Validation

<c-form-input
    id="email"
    name="email"
    label="Email Address"
    type="email"
    value={email}
    required={true}
    oninputchange={handleChange}>
</c-form-input>

Textarea

<c-form-input
    name="comments"
    label="Comments"
    multiline={true}
    rows="5"
    value={comments}
    oninputchange={handleChange}>
</c-form-input>

Programmatic Validation

// In parent component
handleSubmit() {
    const input = this.template.querySelector('c-form-input');
    if (!input.validateInput()) {
        // Show error
        return;
    }
    // Submit form
}

// Set external error
input.setError({
    message: 'Username already exists',
    condition: true
});

Changes & History

  • 2025-09-29: Latest version

⚠️ Pre-Go-Live Concerns

CRITICAL - Fix Before Go-Live

None

HIGH - Address Soon After Go-Live

  • No unit tests: Zero coverage for widely-used validation component
  • Email regex too permissive: Allows a@b.c which may not be desired
  • Password requirements not documented to user: Validation regex requires 8+ chars, upper, lower, number, special but user doesn't see requirements
  • No aria-describedby: Error messages should be linked to input for screen readers
  • Events flood parent on typing: cinput fires on every keystroke without debouncing

MEDIUM - Future Enhancement

  • Add show/hide password toggle: For password inputs
  • Add character counter: For maxLength inputs
  • Add debouncing to cinput: Reduce event frequency
  • Add custom validators: Allow parent to pass validation function
  • Add async validation: Support server-side validation

LOW - Monitor

  • classnames utility: Could use standard library or LWC utility
  • Hard-coded error messages: Should use custom labels for i18n
  • No loading state: For async validation scenarios

Maintenance Notes

Complexity: Medium Recommended Review Schedule: Quarterly

Key Maintainer Notes: - Used extensively across all forms in application - Password regex is very strict - document requirements for users - Email regex may need tightening for production use - Component supports external error setting - useful for server-side validation - isDirty flag prevents validation on untouched fields (good UX) - Supports both single-line and multi-line text entry - All events include fieldName and value for easy parent handling - Consider adding debouncing to cinput event if performance issues arise

Browser Compatibility: - Chrome: Latest - Firefox: Latest - Safari: Latest - Mobile: iOS 12+, Android 8+