VMWare Clarity/Angular modal dialogs

      No Comments on VMWare Clarity/Angular modal dialogs

The VMWare Clarity Design System documentation is a bit vague about how modal dialogs should be handled. The examples presented are not really appropriate for real-world applications, where dialogs need to be reusable components, usually containing forms. So, here’s a more realistic example of how to use modal dialogs in clarity applications.

The code is available as a stackblitz at https://stackblitz.com/github/rogerarmstrong/clarity-sample-modal.

What we want to achieve is a modal component which can be called from anywhere in the application and which takes a model object as input and returns a modified model as output (i.e. the dialog does nothing with the object except allow the user to edit it – the caller has control over what to do with the edited model object).

The application is simple – it displays a user’s details and and an edit button which displays a dialog allowing first and last name to be edited.

click the button to show the dialog

edit the user details

user details are updated

The applications consists of two components “home” and “edit-user-dialog”.

In the home component html, we insert the <edit-user-dialog> tag.

<div>First name: {{user.firstName}}</div>
<div>Last name: {{user.lastName}}</div>

<button (click)="modal.open(user)" class="btn btn-primary">Edit user</button>

<edit-user-dialog></edit-user-dialog>

 

In the home component code,  we reference the edit-user-dialog via a @ViewChild. When the Edit User button is clicked, we invoke the open method on the modal, passing the user object we wish to edit. We then subscribe to the onOK event from the modal (which takes the modified user returned from the dialog.

import { Component, ViewChild, AfterViewInit } from "@angular/core";
import { EditUserDialogComponent } from '../edit-user-dialog/edit-user-dialog.component';
import { User } from "../user";

@Component({
    styleUrls: ['./home.component.scss'],
    templateUrl: './home.component.html',
})
export class HomeComponent implements AfterViewInit {
    @ViewChild(EditUserDialogComponent) modal: EditUserDialogComponent;

    user: User = { firstName: "Joe", lastName: "Smith" };

    ngAfterViewInit(): void {
        this.modal.onOK.subscribe(user => {
            this.user = user;
            this.modal.close();
        });
    }
}

The dialog html is simple – a form allowing the properties of the user to be edited.

<form novalidate #f="ngForm" (ngSubmit)="onSubmit();">
    <clr-modal [(clrModalOpen)]="show">
        <div class="modal-title">Edit user</div>

        <div class="modal-body">
            <section class="form-block">
                <div class="form-group ">
                    <label>First name</label>
                    <input type="text" name="firstName" autofocus *ngIf="user" [(ngModel)]="user.firstName" placeholder="first name" (keypress)="onKeyPress($event)" required>
                </div>
                <div class="form-group ">
                    <label>Last name</label>
                    <input type="text" name="lastName" *ngIf="user" [(ngModel)]="user.lastName" placeholder="last name" (keypress)="onKeyPress($event)" required>
                </div>
            </section>
        </div>

        <div class="modal-footer">
            <button (click)="close()" class="btn btn-default">Cancel</button>
            <button type="submit" class="btn btn-primary">OK</button>
        </div>
    </clr-modal>
</form>

The dialog component has an open and close method.

import { Component, OnInit, Input, ViewChildren, ViewChild, AfterViewInit, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
import { User } from '../user';
import { AutofocusDirective } from '../autofocus.directive';

@Component({
  selector: 'edit-user-dialog',
  templateUrl: './edit-user-dialog.component.html',
  styleUrls: ['./edit-user-dialog.component.scss']
})
export class EditUserDialogComponent {
  @ViewChild(AutofocusDirective) autofocus: AutofocusDirective;
  @Output() onOK: EventEmitter<User> = new EventEmitter<User>();

  show = false;

  user: User;

  open(user: User) {
    this.show = true;
    this.user = Object.create(user); // clone the user (we don't want to modify the original in the dialog)

    setTimeout(() => {
      if (this.autofocus) {
        this.autofocus.setFocus();
      }
    }, 0.1);
  }

  close() {
    this.show = false;
  }

  onKeyPress(event) {
    if (event.keyCode === 13) {
      this.onOK.emit(this.user);
    }
  }

  onSubmit() {
    this.onOK.emit(this.user);
  }

}

Notes: setTimeout is needed to reliably set focus in a modal (over repeated opens) because angular has no DOM ready function to tell us when focus can be set.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.