Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ export const LEARNING_OBJECT_ROUTES = {
return `${environment.apiURL}/learning-objects`;
},

/**
* Request to check if a learning object name is available
* @method GET
* @auth required
* @param name - The name to check
* @returns Promise<boolean> - true if name is available, false if duplicate exists
*/
CHECK_NAME_AVAILABILITY(name: string) {
return `${environment.apiURL}/learning-objects/name/check/${encodeURIComponent(name)}`;
},

/**
* Request to update the collection of a learning object
* @method PATCH
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,27 @@ export class LearningObjectService {
.toPromise();
}

/**
* Checks if a learning object name is available (not a duplicate)
*
* @param {string} name - The name to check
* @returns {Promise<boolean>} - true if name is available, false if duplicate exists
* @memberof LearningObjectService
*/
async checkNameAvailability(name: string): Promise<boolean> {
const route = LEARNING_OBJECT_ROUTES.CHECK_NAME_AVAILABILITY(name);
return this.http
.get<{ validName: boolean }>(route, {
headers: this.headers,
withCredentials: true
})
.pipe(
catchError(this.handleError)
)
.toPromise()
.then(response => response.validName);
}

/**
* Method to delete multiple learning objects
*
Expand Down
26 changes: 6 additions & 20 deletions src/app/onion/learning-object-builder/builder-store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -989,14 +989,7 @@ export class BuilderStore {
})
.catch(e => {
this.serviceInteraction$.next(false);
if (e.status === 409) {
// tried to save an object with a name that already exists
this.validator.errors.saveErrors.set(
'name',
'A learning object with this name already exists! The title should be unique within your learning objects.'
);
this.handleServiceError(e, BUILDER_ERRORS.DUPLICATE_OBJECT_NAME);
} else if (e.status === 400) {
if (e.status === 400) {
this.validator.errors.saveErrors.set(
'name',
e.error.message
Expand All @@ -1023,18 +1016,11 @@ export class BuilderStore {
this.serviceInteraction$.next(false);
})
.catch(e => {
if (e.status === 409) {
// tried to save an object with a name that already exists
this.validator.errors.saveErrors.set(
'name',
'A learning object with this name already exists! The title should be unique within your learning objects.'
);
this.handleServiceError(e, BUILDER_ERRORS.DUPLICATE_OBJECT_NAME);
} else if (e.status === 400) {
this.validator.errors.saveErrors.set(
'name',
JSON.parse(e.error).message
);
if (e.status === 400) {
const body = typeof e.error === 'string' ? JSON.parse(e.error) : e.error;
const errorMsg = body?.message?.[0]?.message?.[0] ?? '';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines were added because the update learning object error was being parsed wrong. It would throw [object Object] when a learning object name was updated to have illegal characters in it.


this.validator.errors.saveErrors.set('name', errorMsg);
this.handleServiceError(e, BUILDER_ERRORS.SPECIAL_CHARACTER_NAME);
} else {
this.handleServiceError(e, BUILDER_ERRORS.UPDATE_OBJECT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@
<div title>{{copy.NAME}}</div>
<div directions>{{copy.NAMEPLACEHOLDER}}</div>
<div data>
<input attr.aria-label="{{copy.NAME}} {{copy.NAMEPLACEHOLDER}}" class="name-field" autocomplete="off" type="text" name="name" placeholder="Learning Object Name" (input)="mutateLearningObject({ name: $event.currentTarget.value })" [value]="learningObject.name" id="form"/>
<input
attr.aria-label="{{copy.NAME}} {{copy.NAMEPLACEHOLDER}}"
class="name-field"
autocomplete="off"
type="text"
name="name"
placeholder="Learning Object Name"
(input)="onNameInput($event.currentTarget.value)"
[value]="learningObject.name"
id="form"
/>
</div>
</clark-info-page-metadata>
<hr />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { takeUntil } from 'rxjs/operators';
import { distinctUntilChanged, switchMap, takeUntil, map, catchError } from 'rxjs/operators';
import {
BuilderStore,
BUILDER_ACTIONS as actions
} from '../../builder-store.service';
import { LearningObject, User } from '@entity';
import { COPY } from './info-page.copy';
import { Subject } from 'rxjs';
import { Observable, of, Subject, from } from 'rxjs';
import { LearningObjectValidator } from '../../validators/learning-object.validator';
import { LearningObjectService } from 'app/core/learning-object-module/learning-object/learning-object.service';
@Component({
Expand All @@ -26,6 +26,9 @@ export class InfoPageComponent implements OnInit, OnDestroy {

destroyed$: Subject<void> = new Subject();

// Emits raw name values from the input for per-keystroke checks
private nameChanges$: Subject<string> = new Subject();

constructor(
private store: BuilderStore,
public validator: LearningObjectValidator,
Expand All @@ -44,6 +47,15 @@ export class InfoPageComponent implements OnInit, OnDestroy {
this.selectedLevels = payload.levels || [];
}
});

this.nameChanges$
.pipe(
// Makes it so that it doesn't check a name unless it has changed
distinctUntilChanged(),
switchMap(name => this.checkNameAvailability(name)),
takeUntil(this.destroyed$)
)
.subscribe();
}


Expand All @@ -56,6 +68,35 @@ export class InfoPageComponent implements OnInit, OnDestroy {
this.store.execute(actions.MUTATE_OBJECT, data);
}

onNameInput(value: string) {
this.mutateLearningObject({ name: value });
this.nameChanges$.next(value);
}

private checkNameAvailability(name: string): Observable<void> {
const trimmedName = (name || '').trim();
const duplicateErrorText =
'A learning object with this name already exists! The title should be unique within your learning objects.';

// If the name is empty, skip the check and return early
if (!trimmedName) {
return of(void 0);
}

return from(this.learningObjectService.checkNameAvailability(trimmedName)).pipe(
map(available => {
if (!available) {
this.validator.errors.saveErrors.set('name', duplicateErrorText);
} else {
this.validator.errors.saveErrors.delete('name');
}

this.cd.markForCheck();
}),
catchError(() => of(void 0))
);
}

toggleContributor(user: User) {
let action: number;

Expand Down