Skip to content

Commit

Permalink
feat(components/forms): update sky-file-attachment component to use…
Browse files Browse the repository at this point in the history
… `ControlValueAccessor`; deprecate `validateFn` attribute in favor of custom form control validators; deprecate `fileChange` event in favor of form control `valueChanges` observable (#2603)
  • Loading branch information
Blackbaud-SteveBrush authored Aug 13, 2024
1 parent b23b00d commit 7a74fc7
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
<form [formGroup]="formGroup" class="sky-padding-even-lg">
<form [formGroup]="formGroup">
<sky-file-attachment
acceptedTypes="application/pdf,image/jpeg,image/png,image/gif"
formControlName="attachment"
helpPopoverTitle="Requirements"
hintText="Attach a .pdf, .gif, .png, or .jpeg file."
labelText="Birth certificate"
[helpPopoverContent]="inlineHelpPopover"
[helpPopoverContent]="helpInlinePopoverRef"
[maxFileSize]="maxFileSize"
[validateFn]="validateFile"
(fileChange)="onFileChange($event)"
(fileClick)="onFileClick($event)"
>
@if (customValidationError === 'invalidStartingLetter') {
@if (attachment.errors?.['invalidStartingLetter']) {
<sky-form-error
errorName="invalidStartingLetter"
errorText="You may not upload a file that begins with the letter 'a'."
Expand All @@ -20,7 +18,7 @@
</sky-file-attachment>
</form>

<ng-template #inlineHelpPopover>
<ng-template #helpInlinePopoverRef>
Make sure the birth certificate includes:
<ul>
<li>Full name of child</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,28 @@ import {
FormGroup,
FormsModule,
ReactiveFormsModule,
ValidationErrors,
Validators,
} from '@angular/forms';
import {
SkyFileAttachmentChange,
SkyFileAttachmentClick,
SkyFileAttachmentModule,
SkyFileItem,
} from '@skyux/forms';

/**
* Demonstrates how to create a custom validator function for your form control.
*/
function customValidator(
control: AbstractControl<SkyFileItem | null | undefined>,
): ValidationErrors | null {
const fileItem = control.value;

return fileItem?.file?.name.startsWith('a')
? { invalidStartingLetter: true }
: null;
}

@Component({
standalone: true,
selector: 'app-demo',
Expand All @@ -28,38 +41,24 @@ import {
],
})
export class DemoComponent {
protected attachment: FormControl;
protected customValidationError: string | undefined;
protected formGroup: FormGroup;
protected maxFileSize = 4000000;
protected attachment: FormControl<SkyFileItem | null | undefined>;

get #reactiveFile(): AbstractControl | null {
return this.formGroup.get('attachment');
}
protected formGroup: FormGroup<{
attachment: FormControl<SkyFileItem | null | undefined>;
}>;

protected maxFileSize = 4000000;

constructor() {
this.attachment = new FormControl(undefined, Validators.required);
this.attachment = new FormControl(undefined, {
validators: [Validators.required, customValidator],
});

this.formGroup = inject(FormBuilder).group({
attachment: this.attachment,
});
}

protected onFileChange(result: SkyFileAttachmentChange): void {
const file = result.file;

if (file?.errorType) {
this.#reactiveFile?.setValue(undefined);
} else {
this.#reactiveFile?.setValue(file);
}

if (file && file.errorType === 'validate') {
this.customValidationError = file.errorParam;
} else {
this.customValidationError = undefined;
}
}

protected onFileClick($event: SkyFileAttachmentClick): void {
// Ensure we are only attempting to navigate to locally updated data for download.
if ($event.file.url.startsWith('data:')) {
Expand All @@ -69,8 +68,4 @@ export class DemoComponent {
link.click();
}
}

protected validateFile(file: SkyFileItem): string {
return file.file.name.startsWith('a') ? 'invalidStartingLetter' : '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Pipe, PipeTransform } from '@angular/core';

/**
* Joins an array of IDs with a single space.
* @internal
*/
@Pipe({
name: 'skyFileAttachmentJoinIds',
standalone: true,
})
export class SkyFileAttachmentJoinIdsPipe implements PipeTransform {
public transform(...ids: (string | null | undefined)[]): string | null {
// Remove undefined values and join with a " ".
return ids && ids.filter((id) => id).join(' ');
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<div class="sky-file-attachment-wrapper">
<div
#labelRef="skyId"
class="sky-file-attachment-label-wrapper"
[attr.id]="labelElementId"
skyId
[ngClass]="{
'sky-control-label-required':
!labelText && isRequired && hasLabelComponent
Expand Down Expand Up @@ -41,51 +42,42 @@
(drop)="fileDrop($event)"
>
<div
#fileDropDescriptionRef="skyId"
aria-hidden="true"
class="sky-screen-reader-only"
role="tooltip"
[attr.id]="fileDropDescriptionElementId"
skyId
>
{{ 'skyux_file_attachment_file_upload_drag_or_click' | skyLibResources }}
</div>
<input
#fileInputRef
hidden
tabindex="-1"
type="file"
[attr.accept]="acceptedTypes || null"
[disabled]="disabled"
[required]="isRequired"
(change)="fileChangeEvent($event)"
#fileInput
/>
<ng-container *ngIf="showFileAttachmentButton">
<button
*ngIf="showFileAttachmentButton"
class="sky-file-attachment-btn sky-btn sky-btn-default"
type="button"
skyId
[attr.aria-describedby]="
(hintText ? hintTextEl.id + ' ' : '') + fileDropDescriptionElementId
"
[attr.aria-label]="
value
? ('skyux_file_attachment_button_label_replace_file_label'
| skyLibResources: fileName)
: ('skyux_file_attachment_button_label_choose_file_label'
| skyLibResources)
hintText && hintTextEl.id
| skyFileAttachmentJoinIds: fileDropDescriptionRef.id
"
[attr.aria-labelledby]="
attachButton.id +
' ' +
(labelText
? labelElementId
: hasLabelComponent
? labelComponents?.get(0)?.labelContentId?.id
: undefined)
attachButtonLabelRef.id
| skyFileAttachmentJoinIds
: (labelText
? labelRef.id
: labelComponents?.get(0)?.labelContentId?.id)
"
[disabled]="disabled"
(click)="onDropClicked()"
#attachButton="skyId"
>
<sky-icon icon="folder-open-o" />
{{
Expand All @@ -108,39 +100,32 @@
class="sky-file-attachment-name"
>
<a
*ngIf="value; else noFile"
*ngIf="value; else noFileRef"
[attr.title]="fileName"
(click)="emitClick()"
>
{{ truncatedFileName }}
</a>
</span>
<ng-template #noFile>
<ng-template #noFileRef>
<span class="sky-file-attachment-none sky-deemphasized">
{{ 'skyux_file_attachment_label_no_file_chosen' | skyLibResources }}
</span>
</ng-template>
<button
*ngIf="value"
class="sky-btn sky-btn-borderless sky-file-attachment-delete"
skyId
type="button"
[attr.aria-label]="
'skyux_file_attachment_file_item_remove' | skyLibResources: fileName
"
[attr.aria-labelledby]="
deleteButton.id +
' ' +
(hasLabelComponent
? labelComponents?.get(0)?.labelContentId?.id
: undefined)
deleteButtonLabelRef.id
| skyFileAttachmentJoinIds
: labelComponents?.get(0)?.labelContentId?.id
"
[disabled]="disabled"
[skyThemeClass]="{
'sky-btn-icon-borderless': 'modern'
}"
(click)="deleteFileAttachment()"
#deleteButton="skyId"
>
<sky-icon icon="trash-o" size="md" />
</button>
Expand Down Expand Up @@ -202,3 +187,27 @@
<ng-template #labelContent>
<ng-content select="sky-file-attachment-label" />
</ng-template>

<span
#attachButtonLabelRef="skyId"
aria-hidden="true"
class="sky-screen-reader-only"
skyId
>
{{
value
? ('skyux_file_attachment_button_label_replace_file_label'
| skyLibResources: fileName)
: ('skyux_file_attachment_button_label_choose_file_label'
| skyLibResources)
}}
</span>

<span
#deleteButtonLabelRef="skyId"
aria-hidden="true"
class="sky-screen-reader-only"
skyId
>
{{ 'skyux_file_attachment_file_item_remove' | skyLibResources: fileName }}
</span>
Loading

0 comments on commit 7a74fc7

Please sign in to comment.