From 4d8e861d19af042d9a31c55bf67c58b344430aad Mon Sep 17 00:00:00 2001 From: ievavold <ievavold@wisc.edu> Date: Wed, 22 May 2019 11:04:45 -0500 Subject: [PATCH] ROENROLL-1765 fix double click on course item opening 2 detail modals Bug was caused because the course details modal didn't open until the course details API call had resolved. If the user double clicked on the course item, two clicks were registered before the first modal had opened which trigged a second modal to open. Now the course details modal always opens immediately (thus preventing subsequent clicks from targeting the course item element) and the details modal is open while it waits for the details API to resolve. --- src/app/app.component.ts | 15 -- src/app/core/models/course-details.ts | 141 +++++++++--------- .../course-details-dialog.component.html | 22 +++ .../course-details-dialog.component.ts | 53 +++---- .../degree-planner/services/api.service.ts | 4 +- .../course-item/course-item.component.ts | 18 +-- .../course-details.component.ts | 8 +- 7 files changed, 130 insertions(+), 131 deletions(-) create mode 100644 src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.html diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f00fbf8..3c9473c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -9,7 +9,6 @@ import { } from '@angular/forms'; import { Course } from '@app/core/models/course'; import { DegreePlannerApiService } from '@app/degree-planner/services/api.service'; -import { CourseDetailsDialogComponent } from '@app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component'; @Component({ selector: 'cse-root', @@ -48,18 +47,4 @@ export class AppComponent implements OnInit { document.dispatchEvent(customEvent); }); } - - openCourseDetailsDialog(course) { - this.api - .getCourseDetails( - course.payload.subject.subjectCode, - course.payload.courseId, - ) - .subscribe(courseDetails => { - this.dialog.open(CourseDetailsDialogComponent, { - data: { courseDetails: courseDetails }, - closeOnNavigation: true, - }); - }); - } } diff --git a/src/app/core/models/course-details.ts b/src/app/core/models/course-details.ts index 6880692..54a81f1 100644 --- a/src/app/core/models/course-details.ts +++ b/src/app/core/models/course-details.ts @@ -1,97 +1,96 @@ - export interface CourseDetails { - termCode: string; - courseId: string; - subject: Subject; - catalogNumber: string; - approvedForTopics: boolean; - topics: any[]; - minimumCredits: number; - maximumCredits: number; - creditRange: string; - firstTaught: string; - lastTaught: string; - typicallyOffered: string; - generalEd?: any; - ethnicStudies?: any; - breadths: Breadth[]; - lettersAndScienceCredits: LettersAndScienceCredits; - workplaceExperience?: any; - foreignLanguage?: any; - honors?: any; - levels: Level[]; - openToFirstYear: boolean; - advisoryPrerequisites?: any; - enrollmentPrerequisites: string; - allCrossListedSubjects: any[]; - title: string; - description: string; - catalogPrintFlag: boolean; - academicGroupCode?: any; - currentlyTaught: boolean; - gradingBasis: GradingBasis; - repeatable: string; - gradCourseWork: boolean; - instructorProvidedContent?: any; - courseRequirements: any; - courseDesignation: string; - courseDesignationRaw: string; - fullCourseDesignation: string; - fullCourseDesignationRaw: string; - lastUpdated: number; - catalogSort: string; - subjectAggregate: string; - titleSuggest: TitleSuggest; + termCode: string; + courseId: string; + subject: Subject; + catalogNumber: string; + approvedForTopics: boolean; + topics: any[]; + minimumCredits: number; + maximumCredits: number; + creditRange: string; + firstTaught: string; + lastTaught: string; + typicallyOffered: string; + generalEd?: any; + ethnicStudies?: any; + breadths: Breadth[]; + lettersAndScienceCredits: LettersAndScienceCredits; + workplaceExperience?: any; + foreignLanguage?: any; + honors?: any; + levels: Level[]; + openToFirstYear: boolean; + advisoryPrerequisites?: any; + enrollmentPrerequisites: string; + allCrossListedSubjects: any[]; + title: string; + description: string; + catalogPrintFlag: boolean; + academicGroupCode?: any; + currentlyTaught: boolean; + gradingBasis: GradingBasis; + repeatable: string; + gradCourseWork: boolean; + instructorProvidedContent?: any; + courseRequirements: any; + courseDesignation: string; + courseDesignationRaw: string; + fullCourseDesignation: string; + fullCourseDesignationRaw: string; + lastUpdated: number; + catalogSort: string; + subjectAggregate: string; + titleSuggest: TitleSuggest; } export interface SchoolCollege { - academicOrgCode: string; - academicGroupCode: string; - shortDescription: string; - formalDescription: string; - uddsCode?: any; - schoolCollegeURI: string; + academicOrgCode: string; + academicGroupCode: string; + shortDescription: string; + formalDescription: string; + uddsCode?: any; + schoolCollegeURI: string; } export interface Subject { - termCode: string; - subjectCode: string; - description: string; - shortDescription: string; - formalDescription: string; - undergraduateCatalogURI: string; - graduateCatalogURI: string; - departmentURI: string; - uddsFundingSource: string; - schoolCollege: SchoolCollege; - footnotes: string[]; + termCode: string; + subjectCode: string; + description: string; + shortDescription: string; + formalDescription: string; + undergraduateCatalogURI: string; + graduateCatalogURI: string; + departmentURI: string; + uddsFundingSource: string; + schoolCollege: SchoolCollege; + footnotes: string[]; } export interface Breadth { - code: string; - description: string; + code: string; + description: string; } export interface LettersAndScienceCredits { - code: string; - description: string; + code: string; + description: string; } export interface Level { - code: string; - description: string; + code: string; + description: string; } export interface GradingBasis { - code: string; - description: string; + code: string; + description: string; } export interface Payload { - courseId: string; + courseId: string; } export interface TitleSuggest { - input: string[]; - payload: Payload; + input: string[]; + payload: Payload; } diff --git a/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.html b/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.html new file mode 100644 index 0000000..eae4ea7 --- /dev/null +++ b/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.html @@ -0,0 +1,22 @@ +<mat-toolbar color="primary" class="dialog-toolbar"> + <span class="dialog-toolbar-title">Course Details</span> + <button + mat-button + aria-label="Close course details dialog" + mat-dialog-close + class="close-btn"> + <i + class="material-icons" + alt="Close course details dialog" + matTooltip="Close course details dialog" + matTooltipPosition="above"> + clear + </i> + </button> +</mat-toolbar> + +<mat-dialog-content class="mat-typography dialog-with-toolbar"> + <ng-container *ngIf="(data$ | async) as data"> + <cse-course-details [courseDetails]="data" [type]="type"></cse-course-details> + </ng-container> +</mat-dialog-content> diff --git a/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.ts b/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.ts index 47d3937..cfc3998 100644 --- a/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.ts +++ b/src/app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component.ts @@ -1,33 +1,34 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material'; +import { Observable } from 'rxjs'; +import { CourseDetails } from '@app/core/models/course-details'; +import { DegreePlannerApiService } from '@app/degree-planner/services/api.service'; @Component({ selector: 'cse-course-details-dialog', - template: ` - <mat-toolbar color="primary" class="dialog-toolbar"> - <span class="dialog-toolbar-title">Course Details</span> - <button - mat-button - aria-label="Close course details dialog" - mat-dialog-close - class="close-btn" - > - <i - class="material-icons" - alt="Close course details dialog" - matTooltip="Close course details dialog" - matTooltipPosition="above" - >clear</i - > - </button> - </mat-toolbar> - - <mat-dialog-content class="mat-typography dialog-with-toolbar"> - <cse-course-details></cse-course-details> - </mat-dialog-content> - `, + templateUrl: './course-details-dialog.component.html', }) export class CourseDetailsDialogComponent implements OnInit { - constructor() {} + private subjectCode: string; + private courseId: string; + public type: 'course' | 'search' | 'saved'; + public data$: Observable<CourseDetails>; + + constructor( + @Inject(MAT_DIALOG_DATA) + data: { + subjectCode: string; + courseId: string; + type: CourseDetailsDialogComponent['type']; + }, + private api: DegreePlannerApiService, + ) { + this.subjectCode = data.subjectCode; + this.courseId = data.courseId; + this.type = data.type; + } - ngOnInit() {} + public ngOnInit() { + this.data$ = this.api.getCourseDetails(this.subjectCode, this.courseId); + } } diff --git a/src/app/degree-planner/services/api.service.ts b/src/app/degree-planner/services/api.service.ts index 20f9b7d..5ed9a69 100644 --- a/src/app/degree-planner/services/api.service.ts +++ b/src/app/degree-planner/services/api.service.ts @@ -125,8 +125,8 @@ export class DegreePlannerApiService { getCourseDetails( subjectCode: string, courseId: string, - ): Observable<CourseDetails[]> { - return this.http.get<CourseDetails[]>( + ): Observable<CourseDetails> { + return this.http.get<CourseDetails>( environment.apiSearchUrl + '/course/0000/' + subjectCode + '/' + courseId, HTTP_OPTIONS, ); diff --git a/src/app/degree-planner/shared/course-item/course-item.component.ts b/src/app/degree-planner/shared/course-item/course-item.component.ts index 18fe4f9..b58b8d4 100644 --- a/src/app/degree-planner/shared/course-item/course-item.component.ts +++ b/src/app/degree-planner/shared/course-item/course-item.component.ts @@ -315,17 +315,13 @@ export class CourseItemComponent implements OnInit { return; } - this.api - .getCourseDetails(subjectCode, courseId) - .subscribe(courseDetails => { - const dialogRef = this.dialog.open(CourseDetailsDialogComponent, { - maxWidth: '800px', - width: '80%', - panelClass: this.mobileView.matches ? 'dialog-fullscreen' : '', - data: { courseDetails: courseDetails, courseType: this.type }, - closeOnNavigation: true, - }); - }); + this.dialog.open(CourseDetailsDialogComponent, { + maxWidth: '800px', + width: '80%', + panelClass: this.mobileView.matches ? 'dialog-fullscreen' : '', + data: { subjectCode, courseId, type: this.type }, + closeOnNavigation: true, + }); } // Check for enter key presses diff --git a/src/app/shared/components/course-details/course-details.component.ts b/src/app/shared/components/course-details/course-details.component.ts index ef6d419..1cb25f7 100644 --- a/src/app/shared/components/course-details/course-details.component.ts +++ b/src/app/shared/components/course-details/course-details.component.ts @@ -31,9 +31,8 @@ const isntUndefined = <T>(thing: T | undefined): thing is T => { styleUrls: ['./course-details.component.scss'], }) export class CourseDetailsComponent implements OnInit, OnDestroy { - @Input() termCode: TermCode; - public courseDetails: CourseDetails; - public type: 'course' | 'search' | 'saved'; + @Input() public courseDetails: CourseDetails; + @Input() public type: 'course' | 'search' | 'saved'; public selectedSearchTerm: TermCode | undefined; public term$: Observable<PlannedTerm>; public termSelector: FormGroup; @@ -46,14 +45,11 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { public plannedCourses: ReadonlyArray<Course>; constructor( - @Inject(MAT_DIALOG_DATA) public data: any, private store: Store<GlobalState>, private fb: FormBuilder, public dialog: MatDialog, public mediaMatcher: MediaMatcher, ) { - this.courseDetails = data.courseDetails; - this.type = data.courseType; this.mobileView = mediaMatcher.matchMedia('(max-width: 959px)'); } -- GitLab