Skip to content

Public Form

Chris Olsen edited this page Apr 14, 2025 · 1 revision

Getting started

Define your pages

type Page = "personal-details" | "address" | "summary"

Initialize page component

export class ApplicationComponent implements OnInit {

	// Define your controller with the `Page` type defined above
    _controller: PublicFormController<Page>

	// Initialize your controller with the controller type
	// details - a typical form
	// list - when you have a list of many subitems ex. children
    constructor(private router: Router) {
        this._controller = new PublicFormController("details");
    }

    ngOnInit(): void {
        (async () => {
	        // fetch your data
            const req = await fetch("http://some_url/my-form")
            const raw = await req.text();
            this._controller.initState(item?.data || {}, () => {
                this.formStatus = "complete";
                this.showSpinner = false;
            });
        })();
    }
}

Add empty validators

export class ApplicationComponent implements OnInit {

	// ...
	
    handlePersonalDetails(e: Event): Page | undefined {
        return "address";
    }

    handleAddress(e: Event): Page | undefined {
        return "summary";
    }
}

Handle PublicForm events

Properties

status: (required) set to complete once external data is fetched _complete: *(required)*callback when last page is completed _init: (required) should be instantiated within controller _stateChange: (optional) allows you to incrementally save your data

<goa-public-form
  [status]="formStatus"
  (_complete)="onComplete()"
  (_init)="_controller.init($event)"
  (_stateChange)="updateState($event)"

	<!-- content -->

</goa-public-form>
export class ApplicationComponent implements OnInit {

	// set to complete in the _controller.initState callback
	formStatus: "initializing" | "complete" = "initializing";

	// ...
	
    async updateState(e: Event) {
        this._controller.updateObjectState(e);
        
        // send state do your API  here
    }

    onComplete() {
	   // called when the button on the last page of the form is clicked
    }
}

Create your page steps

goa-public-form-page

id: (required) An id defined within the Pagestype _continue: (required) Callback to trigger page handler functions. The second argument, must match the id prop. section-title: (optional) Defines a section of the form heading: (optional) Heading text of the page. Most often used when more than one field exists on the page. type: (default: step) step | summary | multistep The page type. back-url: (optional) If set, will result in navigating to the url when a user clicks the Back link instead of the default behaviour of navigating to the previous question page. summary-heading: (optional) Will be shown as a heading in the final summary page.

goa-form-item

name: (optional) The text displayed in the summary next to the value label: (optional) The text that will be displayed in the form

goa-[field] (input, dropdown, etc)

name: (required) The name of the field that the value will be saved by within the JSON payload.

<goa-public-form ...>

  <goa-public-form-page
    id="personal-details"
    section-title="Application"
    heading="Personal details"
    type="first-step"
    back-url="/"
    (_continue)="onPageChange($event, 'personal-details')"
  >
    <goa-fieldset>
      <goa-form-item name="First name">
        <goa-input name="first-name" />
      </goa-form-item>
      <goa-form-item name="Last name">
        <goa-input name="last-name" />
      </goa-form-item>
      <goa-form-item label="SIN" name="Social insuance number">
        <goa-input name="sin" />
      </goa-form-item>
    </goa-fieldset>
  </goa-public-form-page>
  
  <goa-public-form-page
    id="address"
    section-title="Application"
    heading="Your current address"
    type="step"
    (_continue)="onPageChange($event, 'address')"
  >
    <goa-fieldset>
      <goa-form-item name="Address">
        <goa-input name="address" />
      </goa-form-item>
      <goa-form-item name="City/town">
        <goa-input name="city" />
      </goa-form-item>
      <goa-form-item name="Postal code">
        <goa-input name="postal-code" />
      </goa-form-item>
    </goa-fieldset>
  </goa-public-form-page>

  <goa-public-form-page
	id="summary" 
	type="summary"
    section-title="Application"
	heading="Summary"
  > 
	 <goa-public-form-summary /> 
  </goa-public-form-page>

</goa-public-form>

Create the _continue callback

export class ApplicationComponent implements OnInit {

	// ...
	
    onPageChange(e: Event, from: Page) {
        let dest: Page | undefined = undefined;
        switch (from) {
          case "personal-details":
            this.handlePersonalDetails(e);
            break;
          case "address":
            this.handleAddress(e);
            break;
          default:
            console.warn("Unhandled page", from);
            break;
        }

        if (dest) {
          this._controller.continueTo(dest);
        }
    }
}

Add validation to the functions created in step

export class ApplicationComponent implements OnInit {
	// ...

	handlePersonalDetails(e: Event): Page | undefined {
      const [firstNameOk, value] = this._controller.validate(e, "first-name", [
        requiredValidator("First name is required"),
      ]);
      
      const [lastNameOk, value] = this._controller.validate(e, "last-name", [
        requiredValidator("Last name is required"),
      ]);
      
      const [sinOk, value] = this._controller.validate(e, "sin", [
        requiredValidator("Social insurance number is required"),
        SINValidator(),
      ]);

      if (!firstNameOk || !lastNameOk || !sinOk) {
         // exist if invalid 
	     return; 
      }

	  // if valid return the `id` of the page to progress to
	  return "address"
	}
}

Subforms

In the scenario that you need to obtain details for a one-to-many relationship you can use the <goa-sub-form> to do so.

Subform code

export class ApplicationComponent implements OnInit {
	// Add child pages
	type ChildPage = "name" | "dob" | "summary";

	// The new children page set needs to be added to the 
	// top-level app's page list
	type Page = "personal-details" | "address" | "children" | "summary"

	// add child controller
	_childController: PublicFormController<ChildPage>;
  
    constructor(private router: Router) {
	    // instantiate child controller as a `list` type
        this._childController = new PublicFormController("list");
    }

	// add child state change handler
    updateChildrenState(e: Event) {
		this._childFormController.updateListState(e);
    }

	// helper function to get list of children
	children(): Record<string, string>[] {
		return this._childController.getStateList();
	}
}

Subform element

Subform properties

id: (required) Defines the inner form's id _init: (required) Same as PublicForm _stateChange: (optional) Same a PublicForm

  <goa-public-form-page
    id="children"
    type="multistep"
    (_continue)="onPageChange($event, 'children')"
  >
    <goa-public-subform
      id="children-subform"
      (_init)="_childController.initList($event)"
      (_stateChange)="updateChildrenState($event)"
    >
      <goa-public-subform-index
        slot="subform-index"
        section-title="Child's profile"
        heading="Child(ren)'s profile"
        action-button-text="Add Child"
      >
        <goa-text mb="l" *ngIf="children().length === 0">
          Include the child(ren) listed in your court order or agreement that you want MEP to enforce support for.
          Select
          ‘Add child’ to start.
        </goa-text>

        <goa-table width="100%" mb="xl" *ngIf="children().length > 0">
          <thead>
          <tr>
            <th>First name</th>
            <th>Last name</th>
            <th></th>
            <th></th>
          </tr>
          </thead>
          <tr *ngFor="let item of children(); index as i">
            <td>
              {{ item["firstName"] }}
            </td>
            <td>
              {{ item["lastName"] }}
            </td>
            <td class="goa-table-number-header">
              <goa-link-button (_click)="_childController.edit(i)">Edit</goa-link-button>
            </td>
            <td class="goa-table-number-header" style="width: 0px">
              <goa-link-button (_click)="showModal(i)">Delete</goa-link-button>
            </td>
          </tr>
        </goa-table>
      </goa-public-subform-index>

      <goa-public-form-page
        id="name"
        section-title="Child's profile"
        heading="Name"
        sub-heading="Child's name"
        button-text="Continue"
        (_continue)="onChildPageChange($event, 'name')"
      >
        <goa-fieldset>
          <goa-form-item name="First name" label="First name">
            <goa-input name="firstName"></goa-input>
          </goa-form-item>
          <goa-form-item name="Middle name" label="Middle name">
            <goa-input name="middleName"></goa-input>
          </goa-form-item>
          <goa-form-item name="Last name" label="Last name">
            <goa-input name="lastName"></goa-input>
          </goa-form-item>
        </goa-fieldset>
      </goa-public-form-page>

      <goa-public-form-page
        id="dob"
        section-title="Child's profile"
        heading="Your child's birthdate"
        (_continue)="onChildPageChange($event, 'dob')"
      >
        <goa-fieldset>
          <goa-form-item name="Date of birth" label="Date of birth">
            <goa-date-picker type="input" name="dob"></goa-date-picker>
          </goa-form-item>
        </goa-fieldset>
      </goa-public-form-page>

      <goa-public-form-page
        id="complete"
        heading="Summary"
        section-title="Child's profile"
        type="summary"
        button-text="Back to list"
      >
        <goa-public-form-summary></goa-public-form-summary>
      </goa-public-form-page>

    </goa-public-subform>
  </goa-public-form-page>