-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Add confirmation prompt * Remove old form iframe * Improve declaration view spacing * Edit page heading phrasing for clarity * Create request form * Add validation messages * Fix form validation * Set up form submission confirmation * Create submission acknowledgement view * Fix URL checking regex * Fix initial state * Display placeholder when optional field is empty * Fix code style * Edit comment for clarity * Fix institution and country combination Co-authored-by: Jay Aljelo Ting <[email protected]> * Fix naming * Remove hard line break * Add explanatory comment for regex * Remove newline * Add newlines at end of file * Clear styles file * Re-add styles file * Include test * Add test cases for requestSubmissionEvent * Improve test case readability * Edit test case name for clarity * Add snapshot tests * Revert "Add snapshot tests" This reverts commit ec7395d. * Fix lint errors * Rename methods to be clearer * Disable submit button when not ready to submit --------- Co-authored-by: Jay Aljelo Ting <[email protected]>
- Loading branch information
1 parent
cb29108
commit f7eaa61
Showing
9 changed files
with
380 additions
and
11 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
...eb/app/pages-static/request-page/instructor-request-form/instructor-request-form-model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export type InstructorRequestFormModel = { | ||
name: string, | ||
institution: string, | ||
country: string, | ||
email: string, | ||
homePage: string, | ||
comments: string, | ||
}; |
92 changes: 92 additions & 0 deletions
92
.../pages-static/request-page/instructor-request-form/instructor-request-form.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<p aria-hidden="true"> | ||
<!-- aria-hidden as screen readers use inputs' required attribute instead (and cannot see the asterisks) --> | ||
Questions marked with an asterisk <span class="red-font">*</span> are required. | ||
</p> | ||
<form (ngSubmit)="onSubmit()" [formGroup]="arf"> | ||
<div class="form-group {{checkIsFieldRequired(name) ? 'required' : ''}}"> | ||
<label for="name" id="name-label" class="qn"> | ||
Full Name | ||
</label> | ||
<p class="help-block"> | ||
This is the name that will be shown to your students. You may include salutation (Dr. Prof. etc.) | ||
</p> | ||
<input class="form-control {{getFieldValidationClasses(name)}}" type="text" id="name" autocomplete="name" | ||
[formControl]="name" [required]="checkIsFieldRequired(name)" [attr.aria-invalid]="checkIsFieldInvalid(name)"> | ||
<div *ngIf="checkIsFieldInvalid(name)" role="alert" aria-describedby="name-label" tabindex="0" class="invalid-feedback"> | ||
Please enter your name. | ||
</div> | ||
</div> | ||
<br> | ||
<div class="form-group {{checkIsFieldRequired(institution) ? 'required' : ''}}"> | ||
<label for="institution" id="institution-label" class="qn"> | ||
University/school/institution | ||
</label> | ||
<p class="help-block"> | ||
Please give full name of the university/institution. | ||
</p> | ||
<input class="form-control {{getFieldValidationClasses(institution)}}" type="text" id="institution" | ||
autocomplete="organization" [formControl]="institution" [required]="checkIsFieldRequired(institution)" | ||
[attr.aria-invalid]="checkIsFieldInvalid(institution)"> | ||
<div *ngIf="checkIsFieldInvalid(institution)" role="alert" aria-describedby="institution-label" tabindex="0" | ||
class="invalid-feedback"> | ||
Please enter your institution. | ||
</div> | ||
</div> | ||
<br> | ||
<div class="form-group {{checkIsFieldRequired(country) ? 'required' : ''}}"> | ||
<label for="country" id="country-label" class="qn"> | ||
Country | ||
</label> | ||
<p class="help-block"> | ||
Which country is your university/institution based in? | ||
</p> | ||
<input class="form-control {{getFieldValidationClasses(country)}}" type="text" id="country" | ||
autocomplete="country-name" [formControl]="country" [required]="checkIsFieldRequired(country)" | ||
[attr.aria-invalid]="checkIsFieldInvalid(country)"> | ||
<div *ngIf="checkIsFieldInvalid(country)" role="alert" aria-describedby="country-label" tabindex="0" | ||
class="invalid-feedback"> | ||
Please enter your institution's country. | ||
</div> | ||
</div> | ||
<br> | ||
<div class="form-group {{checkIsFieldRequired(email) ? 'required' : ''}}"> | ||
<label for="email" id="email-label" class="qn"> | ||
Official email address | ||
</label> | ||
<p class="help-block"> | ||
Please use the email address <b>given to you by your school/university</b> | ||
(not your personal Gmail/Hotmail address). | ||
Note that this email address will be visible to the students you enroll in TEAMMATES. | ||
</p> | ||
<input class="form-control {{getFieldValidationClasses(email)}}" type="email" id="email" autocomplete="email" | ||
[formControl]="email" [required]="checkIsFieldRequired(email)" [attr.aria-invalid]="checkIsFieldInvalid(email)"> | ||
<div *ngIf="checkIsFieldInvalid(email)" role="alert" aria-describedby="email-label" tabindex="0" | ||
class="invalid-feedback"> | ||
Please enter a valid email address. | ||
</div> | ||
</div> | ||
<br> | ||
<div class="form-group {{checkIsFieldRequired(homePage) ? 'required' : ''}}"> | ||
<label for="homePage" id="homePage-label" class="qn"> | ||
URL of your home page (if any) | ||
</label> | ||
<input class="form-control {{getFieldValidationClasses(homePage)}}" type="url" id="homePage" autocomplete="url" | ||
[formControl]="homePage" [required]="checkIsFieldRequired(homePage)" [attr.aria-invalid]="checkIsFieldInvalid(homePage)"> | ||
<div *ngIf="checkIsFieldInvalid(homePage)" role="alert" aria-describedby="homePage-label" tabindex="0" | ||
class="invalid-feedback"> | ||
Please enter a valid URL. | ||
</div> | ||
</div> | ||
<br> | ||
<div class="form-group {{checkIsFieldRequired(comments) ? 'required' : ''}}"> | ||
<label for="comments" id="comments-label" class="qn"> | ||
Any other comments/queries | ||
</label> | ||
<textarea class="form-control {{getFieldValidationClasses(comments)}}" [formControl]="comments" | ||
[attr.aria-invalid]="checkIsFieldInvalid(comments)"></textarea> | ||
</div> | ||
<br> | ||
<button type="submit" class="btn btn-primary" id="submit-button" [disabled]="!checkCanSubmit()"> | ||
Submit | ||
</button> | ||
</form> |
22 changes: 22 additions & 0 deletions
22
.../pages-static/request-page/instructor-request-form/instructor-request-form.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
label.qn { | ||
font-weight: bold; | ||
font-size: 1rem; | ||
margin-bottom: 0.3rem; | ||
} | ||
|
||
.form-group { | ||
margin-bottom: 0.5rem; | ||
} | ||
|
||
.form-group.required > label::after { | ||
content:"*"; | ||
color: red; | ||
} | ||
|
||
.help-block { | ||
margin-bottom: 0.8rem; | ||
} | ||
|
||
.red-font { | ||
color: red; | ||
} |
76 changes: 76 additions & 0 deletions
76
...ges-static/request-page/instructor-request-form/instructor-request-form.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { ReactiveFormsModule } from '@angular/forms'; | ||
import { By } from '@angular/platform-browser'; | ||
import { first } from 'rxjs'; | ||
import { InstructorRequestFormModel } from './instructor-request-form-model'; | ||
import { InstructorRequestFormComponent } from './instructor-request-form.component'; | ||
|
||
describe('InstructorRequestFormComponent', () => { | ||
let component: InstructorRequestFormComponent; | ||
let fixture: ComponentFixture<InstructorRequestFormComponent>; | ||
const typicalModel: InstructorRequestFormModel = { | ||
name: 'John Doe', | ||
institution: 'Example Institution', | ||
country: 'Example Country', | ||
email: '[email protected]', | ||
homePage: 'xyz.example.edu/john', | ||
comments: '', | ||
}; | ||
|
||
/** | ||
* Fills in form fields with the given data. | ||
* | ||
* @param data Data to fill form with. | ||
*/ | ||
function fillFormWith(data: InstructorRequestFormModel): void { | ||
component.name.setValue(data.name); | ||
component.institution.setValue(data.institution); | ||
component.country.setValue(data.country); | ||
component.email.setValue(data.email); | ||
component.homePage.setValue(data.homePage); | ||
component.comments.setValue(data.comments); | ||
} | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [InstructorRequestFormComponent], | ||
imports: [ReactiveFormsModule], | ||
}); | ||
fixture = TestBed.createComponent(InstructorRequestFormComponent); | ||
component = fixture.componentInstance; | ||
|
||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
it('should emit requestSubmissionEvent once when submit button is clicked', () => { | ||
jest.spyOn(component.requestSubmissionEvent, 'emit'); | ||
|
||
fillFormWith(typicalModel); | ||
const submitButton = fixture.debugElement.query(By.css('#submit-button')); | ||
submitButton.nativeElement.click(); | ||
|
||
expect(component.requestSubmissionEvent.emit).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should emit requestSubmissionEvent with the correct data when form is submitted', () => { | ||
// Listen for emitted value | ||
let actualModel: InstructorRequestFormModel | null = null; | ||
component.requestSubmissionEvent.pipe(first()) | ||
.subscribe((data: InstructorRequestFormModel) => { actualModel = data; }); | ||
|
||
fillFormWith(typicalModel); | ||
component.onSubmit(); | ||
|
||
expect(actualModel).toBeTruthy(); | ||
expect(actualModel!.name).toBe(typicalModel.name); | ||
expect(actualModel!.institution).toBe(typicalModel.institution); | ||
expect(actualModel!.country).toBe(typicalModel.country); | ||
expect(actualModel!.email).toBe(typicalModel.email); | ||
expect(actualModel!.homePage).toBe(typicalModel.homePage); | ||
expect(actualModel!.comments).toBe(typicalModel.comments); | ||
}); | ||
}); |
98 changes: 98 additions & 0 deletions
98
...pp/pages-static/request-page/instructor-request-form/instructor-request-form.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { Component, EventEmitter, Output } from '@angular/core'; | ||
import { FormControl, FormGroup, Validators } from '@angular/forms'; | ||
import { InstructorRequestFormModel } from './instructor-request-form-model'; | ||
|
||
// Use regex to validate URL field as Angular does not have a built-in URL validator | ||
// eslint-disable-next-line | ||
const URL_REGEX = /(https?:\/\/)?(www\.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)|(https?:\/\/)?(www\.)?(?!ww)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/; | ||
|
||
@Component({ | ||
selector: 'tm-instructor-request-form', | ||
templateUrl: './instructor-request-form.component.html', | ||
styleUrls: ['./instructor-request-form.component.scss'], | ||
}) | ||
export class InstructorRequestFormComponent { | ||
|
||
arf = new FormGroup({ | ||
name: new FormControl('', [Validators.required]), | ||
institution: new FormControl('', [Validators.required]), | ||
country: new FormControl('', [Validators.required]), | ||
email: new FormControl('', [Validators.required, Validators.email]), | ||
homePage: new FormControl('', [Validators.pattern(URL_REGEX)]), | ||
comments: new FormControl(''), | ||
}, { updateOn: 'submit' }); | ||
|
||
// Create members for easier access of arf controls | ||
name = this.arf.controls.name; | ||
institution = this.arf.controls.institution; | ||
country = this.arf.controls.country; | ||
email = this.arf.controls.email; | ||
homePage = this.arf.controls.homePage; | ||
comments = this.arf.controls.comments; | ||
|
||
hasSubmitAttempt = false; | ||
|
||
@Output() requestSubmissionEvent = new EventEmitter<InstructorRequestFormModel>(); | ||
|
||
checkIsFieldRequired(field: FormControl): boolean { | ||
return field.hasValidator(Validators.required); | ||
} | ||
|
||
checkIsFieldInvalid(field: FormControl): boolean { | ||
return field.invalid; | ||
} | ||
|
||
checkCanSubmit(): boolean { | ||
return true; // TODO: API integration | ||
} | ||
|
||
getFieldValidationClasses(field: FormControl): string { | ||
let str = ''; | ||
if (this.hasSubmitAttempt) { | ||
if (field.invalid) { | ||
str = 'is-invalid'; | ||
} else if (field.value !== '') { | ||
str = 'is-valid'; | ||
} | ||
} | ||
return str; | ||
} | ||
|
||
onSubmit(): void { | ||
this.hasSubmitAttempt = true; | ||
|
||
if (this.arf.invalid) { | ||
// Do not submit form | ||
return; | ||
} | ||
|
||
const name = this.name.value!.trim(); | ||
const email = this.email.value!.trim(); | ||
const country = this.country.value!.trim(); | ||
const institution = this.institution.value!.trim(); | ||
const combinedInstitution = `${institution}, ${country}`; | ||
const homePage = this.homePage.value!; | ||
const comments = this.comments.value!.trim(); | ||
|
||
const submittedData = { | ||
name, | ||
email, | ||
institution: combinedInstitution, | ||
homePage, | ||
comments, | ||
}; | ||
// TODO: connect to API | ||
// eslint-disable-next-line | ||
submittedData; // PLACEHOLDER | ||
|
||
// Pass form input to parent to display confirmation | ||
this.requestSubmissionEvent.emit({ | ||
name, | ||
institution, | ||
country, | ||
email, | ||
homePage, | ||
comments, | ||
}); | ||
} | ||
} |
74 changes: 63 additions & 11 deletions
74
src/web/app/pages-static/request-page/request-page.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,66 @@ | ||
<h1 class="color-orange"> | ||
Request for an Account | ||
Request for an Instructor Account | ||
</h1> | ||
<div *ngIf="accountRequestFormUrl"> | ||
<p> | ||
Cannot see the request form below? <a [href]="accountRequestFormUrl" target="_blank" rel="noopener noreferrer">Click here.</a> | ||
</p> | ||
<iframe [src]="accountRequestFormUrl" width="760px" height="880px" frameborder="0" marginheight="0" marginwidth="0"> | ||
Loading... | ||
</iframe> | ||
</div> | ||
<div *ngIf="!accountRequestFormUrl"> | ||
The URL for the account request form is not set. | ||
<div class="col-xs-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6"> | ||
<div *ngIf="!submittedFormData"> | ||
<p> | ||
Request for an instructor account using this form if you are an instructor and want to use TEAMMATES to manage peer evaluations and/or other feedback paths of your students. | ||
</p> | ||
<hr> | ||
<div *ngIf="!isDeclarationDone"> | ||
<p> | ||
Note: <b>Students should not use this form to request for TEAMMATES accounts</b>, as students do not need accounts to use TEAMMATES. Instead, TEAMMATES will email students (who have been added to TEAMMATES by a course instructor) an access link when there is a TEAMMATES session available for them to access. | ||
</p> | ||
<a type="button" class="btn btn-secondary" tmRouterLink="/web/front/home">Back to home page</a> | ||
<button type="button" class="btn btn-primary ms-3" (click)="onDeclarationButtonClicked()">I am an instructor</button> | ||
</div> | ||
<div *ngIf="isDeclarationDone"> | ||
<tm-instructor-request-form *ngIf="!submittedFormData" (requestSubmissionEvent)="onRequestSubmitted($event)"></tm-instructor-request-form> | ||
</div> | ||
<hr> | ||
</div> | ||
<div *ngIf="submittedFormData"> | ||
<p> | ||
Your request has been submitted successfully: | ||
</p> | ||
<table class="table table-bordered my-3"> | ||
<tbody> | ||
<tr> | ||
<th scope="row" class="col-3">Full Name</th> | ||
<td>{{submittedFormData.name}}</td> | ||
</tr> | ||
<tr> | ||
<th scope="row">Institution</th> | ||
<td>{{submittedFormData.institution}}</td> | ||
</tr> | ||
<tr> | ||
<th scope="row">Country</th> | ||
<td>{{submittedFormData.country}}</td> | ||
</tr> | ||
<tr> | ||
<th scope="row">Email</th> | ||
<td>{{submittedFormData.email}}</td> | ||
</tr> | ||
<tr> | ||
<th scope="row">Home Page URL</th> | ||
<td> | ||
{{submittedFormData.homePage}} | ||
<span class="empty-field-placeholder" *ngIf="!submittedFormData.homePage"></span> | ||
</td> | ||
</tr> | ||
<tr> | ||
<th scope="row">Comments</th> | ||
<td> | ||
{{submittedFormData.comments}} | ||
<span class="empty-field-placeholder" *ngIf="!submittedFormData.comments"></span> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<p> | ||
We have sent an acknowledgement email to your email address <b>{{submittedFormData.email}}</b>. | ||
Please check your email inbox or spam folder. | ||
If you do not receive the acknowledgement email within 1 hour, please <a tmRouterLink="/web/front/contact">contact</a> us. | ||
</p> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.empty-field-placeholder::after { | ||
content: "(empty)"; | ||
opacity: 0.5; | ||
font-style: italic; | ||
} |
Oops, something went wrong.