MOBILE-4176 grades: Fix 4.1 layout
Fixes some breaking changes introduced in MDL-75513. The fixes included here are not exhaustive but should take care of the basic scenarios covered by e2e tests. Subsequent fixes will be provided to handle other edge-cases.main
parent
d0e3ad6ee8
commit
49e0491428
|
@ -27,13 +27,18 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-container *ngFor="let row of rows">
|
<ng-container *ngFor="let row of rows">
|
||||||
|
<tr *ngIf="!useLegacyLayout && row.itemtype === 'leader'">
|
||||||
|
<td [attr.rowspan]="row.rowspan" class="core-grades-table-leader"></td>
|
||||||
|
</tr>
|
||||||
<tr [attr.role]="row.expandable && showSummary ? 'button row' : 'row'"
|
<tr [attr.role]="row.expandable && showSummary ? 'button row' : 'row'"
|
||||||
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
|
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
|
||||||
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
|
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
|
||||||
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
|
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
|
||||||
[class.core-grades-grade-clickable]="row.expandable && showSummary" [id]="'grade-'+row.id">
|
[class.core-grades-grade-clickable]="row.expandable && showSummary" [id]="'grade-'+row.id"
|
||||||
|
*ngIf="useLegacyLayout || row.itemtype !== 'leader'">
|
||||||
<ng-container *ngIf="row.itemtype">
|
<ng-container *ngIf="row.itemtype">
|
||||||
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
|
<td *ngIf="!useLegacyLayout && row.itemtype == 'category'" class="core-grades-table-category"
|
||||||
|
[attr.rowspan]="row.rowspan">
|
||||||
</td>
|
</td>
|
||||||
<th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan">
|
<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="fas-chevron-right"
|
<ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" name="fas-chevron-right"
|
||||||
|
@ -48,6 +53,7 @@
|
||||||
</core-mod-icon>
|
</core-mod-icon>
|
||||||
<span [innerHTML]="row.gradeitem"></span>
|
<span [innerHTML]="row.gradeitem"></span>
|
||||||
</th>
|
</th>
|
||||||
|
<ng-container *ngIf="row.itemtype !== 'category'">
|
||||||
<ng-container *ngFor="let column of columns">
|
<ng-container *ngFor="let column of columns">
|
||||||
<td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && column.name !== 'grade' &&
|
<td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && column.name !== 'grade' &&
|
||||||
row[column.name] != undefined" [class]="'ion-text-start core-grades-table-' + column.name"
|
row[column.name] != undefined" [class]="'ion-text-start core-grades-table-' + column.name"
|
||||||
|
@ -67,6 +73,7 @@
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
|
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
|
||||||
<td [attr.colspan]="totalColumnsSpan">
|
<td [attr.colspan]="totalColumnsSpan">
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
rows?: CoreGradesFormattedTableRow[];
|
rows?: CoreGradesFormattedTableRow[];
|
||||||
totalColumnsSpan?: number;
|
totalColumnsSpan?: number;
|
||||||
withinSplitView?: boolean;
|
withinSplitView?: boolean;
|
||||||
|
useLegacyLayout?: boolean; // Whether to use the layout before 4.1.
|
||||||
|
|
||||||
protected fetchSuccess = false;
|
protected fetchSuccess = false;
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
this.expandLabel = Translate.instant('core.expand');
|
this.expandLabel = Translate.instant('core.expand');
|
||||||
this.collapseLabel = Translate.instant('core.collapse');
|
this.collapseLabel = Translate.instant('core.collapse');
|
||||||
|
this.useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
|
||||||
|
|
||||||
if (route.snapshot.data.swipeEnabled ?? true) {
|
if (route.snapshot.data.swipeEnabled ?? true) {
|
||||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
|
||||||
|
@ -133,11 +135,22 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
row.expanded = expand ?? !row.expanded;
|
row.expanded = expand ?? !row.expanded;
|
||||||
|
|
||||||
let colspan: number = this.columns.length + (row.colspan ?? 0) - 1;
|
let colspan: number = this.columns.length + (row.colspan ?? 0);
|
||||||
|
|
||||||
|
if (this.useLegacyLayout) {
|
||||||
|
colspan--;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
|
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
|
||||||
const previousRow = this.rows[i];
|
const previousRow = this.rows[i];
|
||||||
|
|
||||||
if (previousRow.expandable || !previousRow.colspan || !previousRow.rowspan || previousRow.colspan !== colspan) {
|
if (
|
||||||
|
!previousRow.rowspan ||
|
||||||
|
!previousRow.colspan ||
|
||||||
|
previousRow.colspan !== colspan ||
|
||||||
|
(!this.useLegacyLayout && previousRow.itemtype !== 'leader') ||
|
||||||
|
(this.useLegacyLayout && previousRow.expandable)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-grades-table-leader {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-no-border {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.dimmed_text,
|
.dimmed_text,
|
||||||
.hidden {
|
.hidden {
|
||||||
opacity: .7;
|
opacity: .7;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
CoreGradesTable,
|
CoreGradesTable,
|
||||||
CoreGradesTableColumn,
|
CoreGradesTableColumn,
|
||||||
CoreGradesTableItemNameColumn,
|
CoreGradesTableItemNameColumn,
|
||||||
|
CoreGradesTableLeaderColumn,
|
||||||
CoreGradesTableRow,
|
CoreGradesTableRow,
|
||||||
} from '@features/grades/services/grades';
|
} from '@features/grades/services/grades';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
@ -71,7 +72,8 @@ export class CoreGradesHelperProvider {
|
||||||
let content = String(column.content);
|
let content = String(column.content);
|
||||||
|
|
||||||
if (name == 'itemname') {
|
if (name == 'itemname') {
|
||||||
await this.setRowIcon(row, content);
|
this.setRowIconAndType(row, content);
|
||||||
|
|
||||||
row.link = this.getModuleLink(content);
|
row.link = this.getModuleLink(content);
|
||||||
row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : '';
|
row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : '';
|
||||||
row.rowclass += column.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
|
row.rowclass += column.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
|
||||||
|
@ -96,10 +98,23 @@ export class CoreGradesHelperProvider {
|
||||||
* Formats a row from the grades table to be rendered in one table.
|
* Formats a row from the grades table to be rendered in one table.
|
||||||
*
|
*
|
||||||
* @param tableRow JSON object representing row of grades table data.
|
* @param tableRow JSON object representing row of grades table data.
|
||||||
|
* @param useLegacyLayout Whether to use the layout before 4.1.
|
||||||
* @return Formatted row object.
|
* @return Formatted row object.
|
||||||
*/
|
*/
|
||||||
protected formatGradeRowForTable(tableRow: CoreGradesTableRow): CoreGradesFormattedTableRow {
|
protected formatGradeRowForTable(tableRow: CoreGradesTableRow, useLegacyLayout: boolean): CoreGradesFormattedTableRow {
|
||||||
const row: CoreGradesFormattedTableRow = {};
|
const row: CoreGradesFormattedTableRow = {};
|
||||||
|
|
||||||
|
if (!useLegacyLayout && 'leader' in tableRow) {
|
||||||
|
const row = {
|
||||||
|
itemtype: 'leader',
|
||||||
|
rowspan: tableRow.leader?.rowspan,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setRowEvenOddClass(row, (tableRow.leader as CoreGradesTableLeaderColumn).class);
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
for (let name in tableRow) {
|
for (let name in tableRow) {
|
||||||
const column: CoreGradesTableColumn = tableRow[name];
|
const column: CoreGradesTableColumn = tableRow[name];
|
||||||
|
|
||||||
|
@ -116,13 +131,13 @@ export class CoreGradesHelperProvider {
|
||||||
row.colspan = itemNameColumn.colspan;
|
row.colspan = itemNameColumn.colspan;
|
||||||
row.rowspan = tableRow.leader?.rowspan || 1;
|
row.rowspan = tableRow.leader?.rowspan || 1;
|
||||||
|
|
||||||
this.setRowIcon(row, content);
|
this.setRowIconAndType(row, content);
|
||||||
row.rowclass = itemNameColumn.class.indexOf('leveleven') < 0 ? 'odd' : 'even';
|
this.setRowEvenOddClass(row, itemNameColumn.class);
|
||||||
row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : '';
|
row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : '';
|
||||||
row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
|
row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
|
||||||
|
|
||||||
content = content.replace(/<\/span>/gi, '\n');
|
content = content.replace(/<\/span>/gi, '\n');
|
||||||
content = CoreTextUtils.cleanTags(content);
|
content = CoreTextUtils.cleanTags(content, true);
|
||||||
name = 'gradeitem';
|
name = 'gradeitem';
|
||||||
} else if (name === 'grade') {
|
} else if (name === 'grade') {
|
||||||
// Add the pass/fail class if present.
|
// Add the pass/fail class if present.
|
||||||
|
@ -202,7 +217,7 @@ export class CoreGradesHelperProvider {
|
||||||
feedback: false,
|
feedback: false,
|
||||||
contributiontocoursetotal: false,
|
contributiontocoursetotal: false,
|
||||||
};
|
};
|
||||||
formatted.rows = table.tabledata.map(row => this.formatGradeRowForTable(row));
|
formatted.rows = this.formatGradesTableRows(table.tabledata);
|
||||||
|
|
||||||
// Get a row with some info.
|
// Get a row with some info.
|
||||||
let normalRow = formatted.rows.find(
|
let normalRow = formatted.rows.find(
|
||||||
|
@ -234,6 +249,33 @@ export class CoreGradesHelperProvider {
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format table rows.
|
||||||
|
*
|
||||||
|
* @param rows Unformatted rows.
|
||||||
|
* @returns Formatted rows.
|
||||||
|
*/
|
||||||
|
protected formatGradesTableRows(rows: CoreGradesTableRow[]): CoreGradesFormattedTableRow[] {
|
||||||
|
const useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
|
||||||
|
const formattedRows = rows.map(row => this.formatGradeRowForTable(row, useLegacyLayout));
|
||||||
|
|
||||||
|
if (!useLegacyLayout) {
|
||||||
|
for (let index = 0; index < formattedRows.length - 1; index++) {
|
||||||
|
const row = formattedRows[index];
|
||||||
|
const previousRow = formattedRows[index - 1] ?? null;
|
||||||
|
|
||||||
|
if (row.itemtype !== 'leader') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.colspan = previousRow.colspan;
|
||||||
|
previousRow.rowclass = `${previousRow.rowclass ?? ''} ion-no-border`.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedRows;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get course data for grades since they only have courseid.
|
* Get course data for grades since they only have courseid.
|
||||||
*
|
*
|
||||||
|
@ -474,7 +516,7 @@ export class CoreGradesHelperProvider {
|
||||||
// Find href containing "/mod/xxx/xxx.php".
|
// Find href containing "/mod/xxx/xxx.php".
|
||||||
const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/;
|
const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/;
|
||||||
|
|
||||||
return table.tabledata.filter((row) => {
|
return this.formatGradesTableRows(table.tabledata.filter((row) => {
|
||||||
if (row.itemname && row.itemname.content) {
|
if (row.itemname && row.itemname.content) {
|
||||||
const matches = row.itemname.content.match(regex);
|
const matches = row.itemname.content.match(regex);
|
||||||
|
|
||||||
|
@ -486,7 +528,7 @@ export class CoreGradesHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}).map((row) => this.formatGradeRowForTable(row));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -582,14 +624,25 @@ export class CoreGradesHelperProvider {
|
||||||
return CoreGrades.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId);
|
return CoreGrades.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 'odd' or 'even' classes into a row.
|
||||||
|
*
|
||||||
|
* @param row Row.
|
||||||
|
* @param classes Existing row classes.
|
||||||
|
*/
|
||||||
|
protected setRowEvenOddClass(row: CoreGradesFormattedTableRow, classes: string): void {
|
||||||
|
const level = parseInt(classes.match(/(?:^|\s)level(\d+)(?:$|\s)/)?.[1] ?? '0');
|
||||||
|
|
||||||
|
row.rowclass = `${row.rowclass ?? ''} ${level % 2 === 0 ? 'even' : 'odd'}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the image and sets it to the row.
|
* Parses the image and sets it to the row.
|
||||||
*
|
*
|
||||||
* @param row Formatted grade row object.
|
* @param row Row.
|
||||||
* @param text HTML where the image will be rendered.
|
* @param text Row content.
|
||||||
* @return Row object with the image.
|
|
||||||
*/
|
*/
|
||||||
protected setRowIcon<T extends CoreGradesFormattedRowCommonData>(row: T, text: string): T {
|
protected setRowIconAndType(row: CoreGradesFormattedRowCommonData, text: string): void {
|
||||||
text = text.replace('%2F', '/').replace('%2f', '/');
|
text = text.replace('%2F', '/').replace('%2f', '/');
|
||||||
if (text.indexOf('/agg_mean') > -1) {
|
if (text.indexOf('/agg_mean') > -1) {
|
||||||
row.itemtype = 'agg_mean';
|
row.itemtype = 'agg_mean';
|
||||||
|
@ -603,7 +656,7 @@ export class CoreGradesHelperProvider {
|
||||||
row.itemtype = 'outcome';
|
row.itemtype = 'outcome';
|
||||||
row.icon = 'fas-tasks';
|
row.icon = 'fas-tasks';
|
||||||
row.iconAlt = Translate.instant('core.grades.outcome');
|
row.iconAlt = Translate.instant('core.grades.outcome');
|
||||||
} else if (text.indexOf('i/folder') > -1 || text.indexOf('fa-folder') > -1) {
|
} else if (text.indexOf('i/folder') > -1 || text.indexOf('fa-folder') > -1 || text.indexOf('category-content') > -1) {
|
||||||
row.itemtype = 'category';
|
row.itemtype = 'category';
|
||||||
row.icon = 'fas-folder';
|
row.icon = 'fas-folder';
|
||||||
row.iconAlt = Translate.instant('core.grades.category');
|
row.iconAlt = Translate.instant('core.grades.category');
|
||||||
|
@ -643,8 +696,6 @@ export class CoreGradesHelperProvider {
|
||||||
row.iconAlt = Translate.instant('core.unknown');
|
row.iconAlt = Translate.instant('core.unknown');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue