From 833f65c6289604fcf083d8b6377bc6793062c7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 6 May 2021 14:38:48 +0200 Subject: [PATCH] MOBILE-3745 buttons: Implement user avatar as a button --- src/core/classes/aria-role-button.ts | 68 +++++++++++++++++++ .../user-avatar/core-user-avatar.html | 12 +++- .../components/user-avatar/user-avatar.ts | 48 ++++++++++--- 3 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 src/core/classes/aria-role-button.ts diff --git a/src/core/classes/aria-role-button.ts b/src/core/classes/aria-role-button.ts new file mode 100644 index 000000000..b55fbc876 --- /dev/null +++ b/src/core/classes/aria-role-button.ts @@ -0,0 +1,68 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export abstract class CoreAriaRoleButton { + + componentInstance: T; + + constructor(componentInstance: T) { + this.componentInstance = componentInstance; + } + + /** + * A11y key functionallity that prevents keyDown events. + * + * @param event Event. + */ + keyDown(event: KeyboardEvent): void { + if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) { + event.preventDefault(); + event.stopPropagation(); + } + } + + /** + * A11y key functionallity that translates space and enter keys to click action. + * + * @param event Event. + */ + keyUp(event: KeyboardEvent): void { + if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) { + event.preventDefault(); + event.stopPropagation(); + + this.click(event); + } + } + + /** + * A11y click functionallity. + * + * @param event Event. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + click(event?: Event): void { + // Nothing defined here. + } + + /** + * Checks if action is allowed in class. + * + * @returns If allowed. + */ + isAllowed(): boolean { + return true; + } + +} diff --git a/src/core/components/user-avatar/core-user-avatar.html b/src/core/components/user-avatar/core-user-avatar.html index a5a776577..60b0a32e2 100644 --- a/src/core/components/user-avatar/core-user-avatar.html +++ b/src/core/components/user-avatar/core-user-avatar.html @@ -1,10 +1,16 @@ + onError="this.src='assets/img/user-avatar.png'" (click)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" + [attr.role]="linkProfile ? 'button' : null" (keydown)="buttonAction.keyDown($event)" + (keyup)="buttonAction.keyUp($event)" + [attr.tabindex]="linkProfile ? 0 : null"> + (click)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null" + (keydown)="buttonAction.keyDown($event)" (keyup)="buttonAction.keyUp($event)" + [attr.tabindex]="linkProfile ? 0 : null"> - + + diff --git a/src/core/components/user-avatar/user-avatar.ts b/src/core/components/user-avatar/user-avatar.ts index dd3884996..6097a0371 100644 --- a/src/core/components/user-avatar/user-avatar.ts +++ b/src/core/components/user-avatar/user-avatar.ts @@ -20,6 +20,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreUserProvider, CoreUserBasicData } from '@features/user/services/user'; import { CoreNavigator } from '@services/navigator'; +import { CoreAriaRoleButton } from '@classes/aria-role-button'; /** * Component to display a "user avatar". @@ -36,15 +37,17 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { @Input() user?: CoreUserWithAvatar; // The following params will override the ones in user object. @Input() profileUrl?: string; - @Input() protected linkProfile = true; // Avoid linking to the profile if wanted. + @Input() linkProfile = true; // Avoid linking to the profile if wanted. @Input() fullname?: string; - @Input() protected userId?: number; // If provided or found it will be used to link the image to the profile. - @Input() protected courseId?: number; + @Input() userId?: number; // If provided or found it will be used to link the image to the profile. + @Input() courseId?: number; @Input() checkOnline = false; // If want to check and show online status. @Input() extraIcon?: string; // Extra icon to show near the avatar. avatarUrl?: string; + buttonAction: CoreUserAvatarButton; + // Variable to check if we consider this user online or not. // @TODO: Use setting when available (see MDL-63972) so we can use site setting. protected timetoshowusers = 300000; // Miliseconds default. @@ -52,6 +55,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { protected pictureObserver: CoreEventObserver; constructor() { + this.currentUserId = CoreSites.getCurrentSiteUserId(); this.pictureObserver = CoreEvents.on( @@ -63,12 +67,15 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { }, CoreSites.getCurrentSiteId(), ); + + this.buttonAction = new CoreUserAvatarButton(this); } /** * Component being initialized. */ ngOnInit(): void { + this.setFields(); } @@ -130,19 +137,14 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { * @param event Click event. */ gotoProfile(event: Event): void { - if (!this.linkProfile || !this.userId) { + if (!this.buttonAction.isAllowed()) { return; } event.preventDefault(); event.stopPropagation(); - CoreNavigator.navigateToSitePath('user', { - params: { - userId: this.userId, - courseId: this.courseId, - }, - }); + this.buttonAction.click(); } /** @@ -154,6 +156,32 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { } +/** + * Helper class to manage rol button. + */ +class CoreUserAvatarButton extends CoreAriaRoleButton { + + /** + * @inheritdoc + */ + click(): void { + CoreNavigator.navigateToSitePath('user', { + params: { + userId: this.componentInstance.userId, + courseId: this.componentInstance.courseId, + }, + }); + } + + /** + * @inheritdoc + */ + isAllowed(): boolean { + return this.componentInstance.linkProfile && !!this.componentInstance.userId; + } + +} + /** * Type with all possible formats of user. */