MOBILE-3934 grades: Move details into accordion

main
Noel De Martin 2022-01-12 11:09:00 +01:00
parent 803ad32ec8
commit 275991d9ef
8 changed files with 236 additions and 378 deletions

View File

@ -22,10 +22,6 @@ const routes: Routes = [
{
path: '',
component: CoreGradesCoursePage,
data: {
useSplitView: false,
outsideGradesTab: true,
},
},
];

View File

@ -22,7 +22,6 @@ import { CoreSharedModule } from '@/core/shared.module';
import { CoreGradesCoursePage } from './pages/course/course.page';
import { CoreGradesCoursePageModule } from './pages/course/course.module';
import { CoreGradesCoursesPage } from './pages/courses/courses.page';
import { CoreGradesGradePage } from './pages/grade/grade.page';
const mobileRoutes: Routes = [
{
@ -33,10 +32,6 @@ const mobileRoutes: Routes = [
path: ':courseId',
component: CoreGradesCoursePage,
},
{
path: ':courseId/:gradeId',
component: CoreGradesGradePage,
},
];
const tabletRoutes: Routes = [
@ -50,16 +45,6 @@ const tabletRoutes: Routes = [
},
],
},
{
path: ':courseId',
component: CoreGradesCoursePage,
children: [
{
path: ':gradeId',
component: CoreGradesGradePage,
},
],
},
];
const routes: Routes = [
@ -75,7 +60,6 @@ const routes: Routes = [
],
declarations: [
CoreGradesCoursesPage,
CoreGradesGradePage,
],
})
export class CoreGradesCoursesLazyModule {}

View File

@ -9,31 +9,36 @@
</ion-toolbar>
</ion-header>
<ion-content>
<core-split-view [mode]="splitViewMode">
<ion-refresher slot="fixed" [disabled]="!grades.loaded" (ionRefresh)="refreshGrades($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="grades.loaded" class="safe-area-padding">
<core-empty-box *ngIf="grades.empty" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate">
</core-empty-box>
<div *ngIf="!grades.empty" class="core-grades-container">
<table cellspacing="0" cellpadding="0" class="core-grades-table">
<thead>
<tr>
<th *ngFor="let column of grades.columns" id="{{column.name}}" class="ion-text-start"
[class.ion-hide-md-down]="column.hiddenPhone" [attr.colspan]="column.colspan">
{{ 'core.grades.' + column.name | translate }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of grades.rows" role="button row" [attr.tabindex]="row.itemtype != 'category' ? 0 : null"
(ariaButtonClick)="row.itemtype != 'category' && grades.select(row)" [class]="row.rowclass"
[ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}'>
<ion-refresher slot="fixed" [disabled]="!columns || !rows" (ionRefresh)="refreshGrades($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="columns && rows" class="safe-area-padding">
<core-empty-box *ngIf="rows && rows.length === 0" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate">
</core-empty-box>
<div *ngIf="rows && rows.length > 0" class="core-grades-container">
<table cellspacing="0" cellpadding="0" class="core-grades-table" [class.summary]="showSummary">
<thead>
<tr>
<th *ngFor="let column of columns" id="{{column.name}}" class="ion-text-start"
[class.ion-hide-md-down]="column.hiddenPhone" [attr.colspan]="column.colspan">
{{ 'core.grades.' + column.name | translate }}
</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let row of rows">
<tr [attr.role]="row.expandable && showSummary ? 'button row' : 'row'"
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
[class.core-grades-grade-clickable]="row.expandable && showSummary">
<ng-container *ngIf="row.itemtype">
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan"></td>
<th class="core-grades-table-gradeitem ion-text-start" [class.column-itemname]="row.itemtype == 'category'"
[attr.aria-current]="grades.getItemAriaCurrent(row)" [attr.colspan]="row.colspan">
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
</td>
<th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan">
<ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start"
[name]="row.expanded ? 'fas-caret-down' : 'fas-caret-right'">
</ion-icon>
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
</ion-icon>
<img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" class="core-module-icon"
@ -43,16 +48,122 @@
</core-mod-icon>
<span [innerHTML]="row.gradeitem"></span>
</th>
<ng-container *ngFor="let column of grades.columns">
<ng-container *ngFor="let column of columns">
<td *ngIf="column.name != 'gradeitem' && row[column.name] != undefined"
[class]="'ion-text-start core-grades-table-' + column.name"
[class.ion-hide-md-down]="column.hiddenPhone" [innerHTML]="row[column.name]"></td>
</ng-container>
</ng-container>
</tr>
</tbody>
</table>
</div>
</core-loading>
</core-split-view>
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
<td [attr.colspan]="totalColumnsSpan">
<ion-list>
<ion-item *ngIf="row.itemname && row.link" class="ion-text-wrap" detail="true" [href]="row.link"
core-link capture="true">
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
</ion-icon>
<img *ngIf="row.image && !row.itemmodule" [src]="row.image && row.itemmodule" slot="start"
[alt]="row.iconAlt" />
<core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start"
[modname]="row.itemmodule">
</core-mod-icon>
<ion-label>
<h2>
<core-format-text [text]="row.itemname" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</h2>
</ion-label>
</ion-item>
<ion-item *ngIf="row.itemname && !row.link" class="ion-text-wrap">
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
</ion-icon>
<img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" [alt]="row.iconAlt" />
<core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start"
[modname]="row.itemmodule">
</core-mod-icon>
<ion-label>
<h2>
<core-format-text [text]="row.itemname" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</h2>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.weight">
<ion-label>
<h2>{{ 'core.grades.weight' | translate}}</h2>
<p [innerHTML]="row.weight"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.grade">
<ion-label>
<h2>{{ 'core.grades.grade' | translate}}</h2>
<p [innerHTML]="row.grade"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.range">
<ion-label>
<h2>{{ 'core.grades.range' | translate}}</h2>
<p [innerHTML]="row.range"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.percentage">
<ion-label>
<h2>{{ 'core.grades.percentage' | translate}}</h2>
<p [innerHTML]="row.percentage"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.lettergrade">
<ion-label>
<h2>{{ 'core.grades.lettergrade' | translate}}</h2>
<p [innerHTML]="row.lettergrade"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.rank">
<ion-label>
<h2>{{ 'core.grades.rank' | translate}}</h2>
<p [innerHTML]="row.rank"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.average">
<ion-label>
<h2>{{ 'core.grades.average' | translate}}</h2>
<p [innerHTML]="row.average"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.feedback">
<ion-label>
<h2>{{ 'core.grades.feedback' | translate}}</h2>
<p>
<core-format-text [maxHeight]="120" [text]="row.feedback" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="row.contributiontocoursetotal">
<ion-label>
<h2>{{ 'core.grades.contributiontocoursetotal' | translate}}</h2>
<p [innerHTML]="row.contributiontocoursetotal"></p>
</ion-label>
</ion-item>
</ion-list>
</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
</core-loading>
</ion-content>

View File

@ -12,23 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { ActivatedRoute, Params } from '@angular/router';
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AfterViewInit, Component, ElementRef } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreGrades } from '@features/grades/services/grades';
import {
CoreGradesFormattedTable,
CoreGradesFormattedTableColumn,
CoreGradesFormattedTableRow,
CoreGradesHelper,
} from '@features/grades/services/grades-helper';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreSplitViewComponent, CoreSplitViewMode } from '@components/split-view/split-view';
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
import { CoreNavigator } from '@services/navigator';
import { CoreScreen } from '@services/screen';
import { Translate } from '@singletons';
/**
* Page that displays a course grades.
@ -38,20 +37,23 @@ import { CoreNavigator } from '@services/navigator';
templateUrl: 'course.html',
styleUrls: ['course.scss'],
})
export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
export class CoreGradesCoursePage implements AfterViewInit {
grades!: CoreGradesCourseManager;
splitViewMode?: CoreSplitViewMode;
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
constructor(protected route: ActivatedRoute) {
let courseId: number;
let userId: number;
courseId!: number;
userId!: number;
expandLabel!: string;
collapseLabel!: string;
columns?: CoreGradesFormattedTableColumn[];
rows?: CoreGradesFormattedTableRow[];
totalColumnsSpan?: number;
withinSplitView?: boolean;
constructor(protected route: ActivatedRoute, protected element: ElementRef<HTMLElement>) {
try {
courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
this.userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
this.expandLabel = Translate.instant('core.expand');
this.collapseLabel = Translate.instant('core.collapse');
} catch (error) {
CoreDomUtils.showErrorModal(error);
@ -59,28 +61,61 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
return;
}
}
const useSplitView = route.snapshot.data.useSplitView ?? true;
const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false;
this.splitViewMode = useSplitView ? undefined : CoreSplitViewMode.MENU_ONLY;
this.grades = new CoreGradesCourseManager(CoreGradesCoursePage, courseId, userId, outsideGradesTab);
get showSummary(): boolean {
return CoreScreen.isMobile || !!this.withinSplitView;
}
/**
* @inheritdoc
*/
async ngAfterViewInit(): Promise<void> {
await this.fetchInitialGrades();
this.withinSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view');
this.grades.start(this.splitView);
await this.fetchInitialGrades();
await CoreGrades.logCourseGradesView(this.courseId, this.userId);
}
/**
* @inheritdoc
* Get aria label for row.
*
* @param row Row.
* @returns Aria label, if applicable.
*/
ngOnDestroy(): void {
this.grades.destroy();
rowAriaLabel(row: CoreGradesFormattedTableRow): string | undefined {
if (!row.expandable || !this.showSummary) {
return;
}
const actionLabel = row.expanded ? this.collapseLabel : this.expandLabel;
return `${actionLabel} ${row.ariaLabel}`;
}
/**
* Toggle whether a row is expanded or collapsed.
*
* @param row Row.
*/
toggleRow(row: CoreGradesFormattedTableRow): void {
if (!this.rows || !this.columns) {
return;
}
row.expanded = !row.expanded;
let colspan: number = this.columns.length + (row.colspan ?? 0) - 1;
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
const previousRow = this.rows[i];
if (previousRow.expandable || !previousRow.colspan || !previousRow.rowspan || previousRow.colspan !== colspan) {
continue;
}
colspan++;
previousRow.rowspan += row.expanded ? 1 : -1;
}
}
/**
@ -89,9 +124,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
* @param refresher Refresher.
*/
async refreshGrades(refresher: IonRefresher): Promise<void> {
const { courseId, userId } = this.grades;
await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(courseId, userId));
await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId));
await CoreUtils.ignoreErrors(this.fetchGrades());
refresher?.complete();
@ -106,7 +139,8 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading course');
this.grades.setTable({ columns: [], rows: [] });
this.columns = [];
this.rows = [];
}
}
@ -114,103 +148,12 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
* Update the table of grades.
*/
private async fetchGrades(): Promise<void> {
const table = await CoreGrades.getCourseGradesTable(this.grades.courseId, this.grades.userId);
const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId);
const formattedTable = await CoreGradesHelper.formatGradesTable(table);
this.grades.setTable(formattedTable);
this.columns = formattedTable.columns;
this.rows = formattedTable.rows;
this.totalColumnsSpan = formattedTable.columns.reduce((total, column) => total + column.colspan, 0);
}
}
/**
* Helper to manage the table of grades.
*/
class CoreGradesCourseManager extends CorePageItemsListManager<CoreGradesFormattedTableRowFilled> {
courseId: number;
userId: number;
columns?: CoreGradesFormattedTableColumn[];
rows?: CoreGradesFormattedTableRow[];
private outsideGradesTab: boolean;
constructor(pageComponent: unknown, courseId: number, userId: number, outsideGradesTab: boolean) {
super(pageComponent);
this.courseId = courseId;
this.userId = userId;
this.outsideGradesTab = outsideGradesTab;
}
/**
* Set grades table.
*
* @param table Grades table.
*/
setTable(table: CoreGradesFormattedTable): void {
this.columns = table.columns;
this.rows = table.rows;
this.setItems(table.rows.filter(this.isFilledRow));
}
/**
* @inheritdoc
*/
async select(row: CoreGradesFormattedTableRowFilled): Promise<void> {
if (this.outsideGradesTab) {
await CoreNavigator.navigateToSitePath(`/grades/${this.courseId}/${row.id}`, {
params: {
userId: this.userId,
},
});
return;
}
return super.select(row);
}
/**
* @inheritdoc
*/
protected getDefaultItem(): CoreGradesFormattedTableRowFilled | null {
return null;
}
/**
* @inheritdoc
*/
protected getItemPath(row: CoreGradesFormattedTableRowFilled): string {
return row.id.toString();
}
/**
* @inheritdoc
*/
protected getItemQueryParams(): Params {
return { userId: this.userId };
}
/**
* @inheritdoc
*/
protected async logActivity(): Promise<void> {
await CoreGrades.logCourseGradesView(this.courseId, this.userId);
}
/**
* Check whether the given row is filled or not.
*
* @param row Grades table row.
* @return Whether the given row is filled or not.
*/
private isFilledRow(row: CoreGradesFormattedTableRow): row is CoreGradesFormattedTableRowFilled {
return 'id' in row;
}
}
export type CoreGradesFormattedTableRowFilled = Omit<CoreGradesFormattedTableRow, 'id'> & {
id: number;
};

View File

@ -61,11 +61,15 @@
background-color: var(--header-background);
}
thead #gradeitem {
@include padding(null, null, null, 23px);
}
tbody th {
font-weight: normal;
}
#gradeitem {
tbody #gradeitem {
@include padding(null, null, null, 5px);
}
@ -131,12 +135,15 @@
}
}
}
ion-list, ion-item::part(native) {
background-color: transparent;
}
&.summary .ion-hide-md-down {
display: none;
opacity: 0;
}
core-split-view.nested .core-grades-table .ion-hide-md-down,
core-split-view.menu-and-content .core-grades-table .ion-hide-md-down {
display: none;
opacity: 0;
}
@include media-breakpoint-down(md) {

View File

@ -1,112 +0,0 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
<h1>{{ 'core.grades.grade' | translate }}</h1>
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!gradeLoaded" (ionRefresh)="refreshGrade($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="gradeLoaded">
<core-empty-box *ngIf="!grade" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate"></core-empty-box>
<ion-list *ngIf="grade">
<ion-item *ngIf="grade.itemname && grade.link" class="ion-text-wrap" detail="true" [href]="grade.link" core-link capture="true">
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" slot="start" [attr.aria-label]="grade.iconAlt"></ion-icon>
<img *ngIf="grade.image && !grade.itemmodule" [src]="grade.image && grade.itemmodule" slot="start" [alt]="grade.iconAlt" />
<core-mod-icon *ngIf="grade.image && grade.itemmodule" [modicon]="grade.image" slot="start" [modname]="grade.itemmodule">
</core-mod-icon>
<ion-label>
<h2>
<core-format-text [text]="grade.itemname" contextLevel="course" [contextInstanceId]="courseId">
</core-format-text>
</h2>
</ion-label>
</ion-item>
<ion-item *ngIf="grade.itemname && !grade.link" class="ion-text-wrap">
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" slot="start" [attr.aria-label]="grade.iconAlt"></ion-icon>
<img *ngIf="grade.image && !grade.itemmodule" [src]="grade.image" slot="start" [alt]="grade.iconAlt" />
<core-mod-icon *ngIf="grade.image && grade.itemmodule" [modicon]="grade.image" slot="start" [modname]="grade.itemmodule">
</core-mod-icon>
<ion-label>
<h2>
<core-format-text [text]="grade.itemname" contextLevel="course" [contextInstanceId]="courseId">
</core-format-text>
</h2>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.weight">
<ion-label>
<h2>{{ 'core.grades.weight' | translate}}</h2>
<p [innerHTML]="grade.weight"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.grade">
<ion-label>
<h2>{{ 'core.grades.grade' | translate}}</h2>
<p [innerHTML]="grade.grade"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.range">
<ion-label>
<h2>{{ 'core.grades.range' | translate}}</h2>
<p [innerHTML]="grade.range"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.percentage">
<ion-label>
<h2>{{ 'core.grades.percentage' | translate}}</h2>
<p [innerHTML]="grade.percentage"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.lettergrade">
<ion-label>
<h2>{{ 'core.grades.lettergrade' | translate}}</h2>
<p [innerHTML]="grade.lettergrade"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.rank">
<ion-label>
<h2>{{ 'core.grades.rank' | translate}}</h2>
<p [innerHTML]="grade.rank"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.average">
<ion-label>
<h2>{{ 'core.grades.average' | translate}}</h2>
<p [innerHTML]="grade.average"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.feedback">
<ion-label>
<h2>{{ 'core.grades.feedback' | translate}}</h2>
<p>
<core-format-text [maxHeight]="120" [text]="grade.feedback" contextLevel="course" [contextInstanceId]="courseId">
</core-format-text>
</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="grade.contributiontocoursetotal">
<ion-label>
<h2>{{ 'core.grades.contributiontocoursetotal' | translate}}</h2>
<p [innerHTML]="grade.contributiontocoursetotal"></p>
</ion-label>
</ion-item>
</ion-list>
</core-loading>
</ion-content>

View File

@ -1,85 +0,0 @@
// (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.
import { Component, OnInit } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreGrades } from '@features/grades/services/grades';
import { CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreNavigator } from '@services/navigator';
/**
* Page that displays activity grade.
*/
@Component({
selector: 'page-core-grades-grade',
templateUrl: 'grade.html',
})
export class CoreGradesGradePage implements OnInit {
courseId!: number;
userId!: number;
gradeId!: number;
grade?: CoreGradesFormattedRow | null;
gradeLoaded = false;
constructor() {
try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
this.gradeId = CoreNavigator.getRequiredRouteNumberParam('gradeId');
this.userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
} catch (error) {
CoreDomUtils.showErrorModal(error);
CoreNavigator.back();
return;
}
}
/**
* @inheritdoc
*/
ngOnInit(): void {
this.fetchGrade();
}
/**
* Fetch all the data required for the view.
*/
async fetchGrade(): Promise<void> {
try {
this.grade = await CoreGradesHelper.getGradeItem(this.courseId, this.gradeId, this.userId);
this.gradeLoaded = true;
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading grade item');
}
}
/**
* Refresh data.
*
* @param refresher Refresher.
*/
async refreshGrade(refresher: IonRefresher): Promise<void> {
await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId));
await CoreUtils.ignoreErrors(this.fetchGrade());
refresher.complete();
}
}

View File

@ -54,6 +54,7 @@ export class CoreGradesHelperProvider {
*
* @param tableRow JSON object representing row of grades table data.
* @return Formatted row object.
* @deprecated since app 4.0
*/
protected async formatGradeRow(tableRow: CoreGradesTableRow): Promise<CoreGradesFormattedRow> {
const row: CoreGradesFormattedRow = {
@ -126,6 +127,13 @@ export class CoreGradesHelperProvider {
content = CoreTextUtils.replaceNewLines(content, '<br>');
}
if (row.itemtype !== 'category') {
row.expandable = true;
row.expanded = false;
row.detailsid = `grade-item-${row.id}-details`;
row.ariaLabel = `${row.gradeitem} (${row.grade})`;
}
if (content == '&nbsp;') {
content = '';
}
@ -280,6 +288,7 @@ export class CoreGradesHelperProvider {
* @param siteId Site ID. If not defined, current site.
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise to be resolved when the grades are retrieved.
* @deprecated since app 4.0
*/
async getGradeItem(
courseId: number,
@ -382,6 +391,7 @@ export class CoreGradesHelperProvider {
* @param table JSON object representing a table with data.
* @param gradeId Grade Object identifier.
* @return Formatted HTML table.
* @deprecated since app 4.0
*/
async getGradesTableRow(table: CoreGradesTable, gradeId: number): Promise<CoreGradesFormattedRow | null> {
if (table.tabledata) {
@ -705,8 +715,12 @@ export type CoreGradesFormattedTable = {
export type CoreGradesFormattedTableRow = CoreGradesFormattedRowCommonData & {
id?: number;
detailsid?: string;
colspan?: number;
gradeitem?: string; // The item returned data.
ariaLabel?: string;
expandable?: boolean;
expanded?: boolean;
};
export type CoreGradesFormattedTableColumn = {