Tehnologii Web/2022-2023/Laborator 5

Introduction edit

In this laboratory, we will implement a sign up page with custom validation.

  • You should create a separate HTML page.
  • You should use the already existing CSS styles from previous lab work.
  • We are going to use Typescript, so the script extension should be .ts
  • Parcel has built-in support for Typescript, so changing the extension should be enough.
  • Typescript is a superset of Javascript. Every piece of code written in Javascript can be understood by Typescript.
  • Typescript has additional checks. So, if you have certain errors in your code that weren't triggered in Javascript, then you should adapt the code for Typescript.
  • Browsers don't support Typescript as a standard. So, your workflow must contain a Typescript compiler.

The Requirements edit

  • First name and last name must have minimum 2 characters each.
  • First name and last name together must have at least 6 characters (with no white-spaces).
  • The email address must valid.
  • User must be at least 14 years old.
  • The password must have at least 8 characters, minimum 1 lowercase, 1 uppercase, 1 digit, and 1 special character !@#$%^?><;'\&*().
  • Passwords must match.
 
Lab assignment - Registration Form with custom validation

Laboratory Steps edit

Step 1 edit

Add the HTML elements in your page. Add the appropriate CSS styles.

<form>
  <input id="firstName" type="text">
  <input id="lastName" type="text">
  <input id="email" type="email">
  <input id="birthday" type="date">
  <input id="password" type="password">
  <input id="confirmPassword" type="password">
</form>

Step 2 edit

In your .ts file add the following Form Data interface.

Make sure if you are using Parcel to change the .js to .ts extension.

Other bundlers won't necessarily allow to include .ts files directly in your HTML page.

interface IFormData {
	firstName: string;
    lastName: string;
    birthday: Date;
    password: string;
    confirmPassword: string;
}

Step 3 Define your first Typescript type. In this case, FormField type will be a string, but a string that represents a key of IFormData interface.

type FormField = keyof IFormData;

Step 4 edit

Create a Typescript class.

formData is a normal field of an instance, fields is static.

Create an instance of our class.

class RegisterFormController {
    formData: IFormData;
    static fields: FormField[] = [
        "firstName", 
        "lastName", 
        "email",
        "birthday", 
        "password", 
        "confirmPassword"
    ];
}

const controller = new RegisterFormController();

Step 5 edit

Add a constructor. We want to update the form data on every input event.

Add the following code snippet. Watch the browser console to see how formData is being updated in real time.

Note

  • using ! would force to bypass the null checks. getElementById can return null.
  • using ! operator is generally a bad practice in Typescript, but in our case, our fields are statically defined and they reflect the ids of the elements.
  • {...object} spread operator would make a clone (flat-clone) of the object.
  • [field] snipped will patch the object in that particular key.
class RegisterFormController {
    formData: IFormData;
    static fields: FormField[] = [
        "firstName", 
        "lastName", 
        "birthday", 
        "password", 
        "confirmPassword"
    ];
  
    constructor() {
      for (const field of RegisterFormController.fields) {
      	this.bindElement(field);
      }
    }
    
    bindElement(field: FormField) {
    	document.getElementById(field)!.addEventListener("input", (event: any) => {
          this.formData = {...this.formData, [field]: event.target.value};
          console.log(this.formData);
          this.onDataChanged(field);
      });
    }
    
    onDataChanged = (field: FormField) {}
  }

Step 6 edit

Add the following validation in onDataChanged method.

The first one was done as an example.

Complete the rest of the validation requirements.

Note

  • == and === have a special meaning in Typescript.
  • You might split the implementation in several methods.
class RegisterFormController {
    formData: IFormData;
    static fields: FormField[] = [
        "firstName", 
        "lastName", 
        "birthday", 
        "password", 
        "confirmPassword"
    ];
  
    constructor() {
      for (const field of RegisterFormController.fields) {
      	this.bindElement(field);
      }
    }
    
    bindElement(field: FormField) {
    	document.getElementById(field)!.addEventListener("input", (event: any) => {
          this.formData = {...this.formData, [field]: event.target.value};
          console.log(this.formData);
          this.onDataChanged(field);
      });
    }
    
    onDataChanged = (field: FormField). => {
    	if (field === "firstName") {
          	if (this.formData.firstName.length < 2) {
            	this.setValidation("firstName", "Your name must have at least 2 characters.");
            } else {
            	this.setValidation("firstName", "Looks good!");
            }
        }
    }
    
    setValidation = (field: FormField, message: string) => {
    	document.getElementById(`${field}-validation`)!.innerHTML = message;
    }
  }
  // /(?=[A-Za-z0-9@#$%^&+!=]+$)^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@#$%^&+!=])(?=.{8,}).*$/g;
  ///^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g;