MOBILE-2326 grades: Course grade item page

main
Pau Ferrer Ocaña 2018-02-07 14:15:53 +01:00
parent 4903b31cb0
commit 38a9f019bb
14 changed files with 628 additions and 73 deletions

View File

@ -54,12 +54,14 @@
@include media-breakpoint-down(md) {
.hidden-phone {
display: none !important;
opacity: 0 !important;
}
}
@include media-breakpoint-up(md) {
.hidden-tablet {
display: none !important;
opacity: 0 !important;
}
}

View File

@ -19,7 +19,7 @@
<tr *ngFor="let row of gradesTable.rows" (click)="row.itemtype != 'category' && gotoGrade(row.id)" [class]="row.rowclass">
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
</td>
<th class="core-grades-table-gradeitem" [attr.colspan]="row.colspan">
<th class="core-grades-table-gradeitem" [attr.colspan]="row.colspan" [class.core-split-item-selected]="gradeId == row.id">
<ion-icon *ngIf="row.icon" name="{{row.icon}}" item-start></ion-icon>
<img *ngIf="row.image" [src]="row.image" item-start/>
<span [innerHTML]="row.gradeitem"></span>

View File

@ -53,12 +53,12 @@ core-grades-course {
}
.odd {
td, th {
td, th, th.core-split-item-selected {
background-color: $gray-lighter;
}
}
.even {
td, th {
td, th, th.core-split-item-selected {
background-color: $white;
}
}
@ -69,4 +69,11 @@ core-grades-course {
}
}
}
}
}
.split-pane-side, .split-pane-main {
core-grades-course .core-grades-table .hidden-phone {
display: none;
opacity: 0;
}
}

View File

@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, Input } from '@angular/core';
import { Component, ViewChild, Input, Optional } from '@angular/core';
import { Content, NavParams, NavController } from 'ionic-angular';
import { CoreGradesProvider } from '../../providers/grades';
import { CoreSitesProvider } from '../../../../providers/sites';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreGradesHelperProvider } from '../../providers/helper';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { CoreAppProvider } from '../../../../providers/app';
/**
* Component that displays a course grades.
@ -31,13 +33,15 @@ export class CoreGradesCourseComponent {
@Input() courseId: number;
@Input() userId: number;
@Input() gradeId?: number;
errorMessage: string;
gradesLoaded = false;
gradesTable: any;
constructor(private gradesProvider: CoreGradesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
private gradesHelper: CoreGradesHelperProvider, private sitesProvider: CoreSitesProvider, private navCtrl: NavController) {
private gradesHelper: CoreGradesHelperProvider, private sitesProvider: CoreSitesProvider, private navCtrl: NavController,
private appProvider: CoreAppProvider, @Optional() private svComponent: CoreSplitViewComponent) {
}
/**
@ -46,6 +50,11 @@ export class CoreGradesCourseComponent {
ngOnInit(): void {
// Get first participants.
this.fetchData().then(() => {
if (this.gradeId) {
// There is the grade to load.
this.gotoGrade(this.gradeId);
}
// Add log in Moodle.
return this.gradesProvider.logCourseGradesView(this.courseId, this.userId);
}).finally(() => {
@ -82,12 +91,37 @@ export class CoreGradesCourseComponent {
}
/**
* Navigate to the grades of the selected item.
* Navigate to the grade of the selected item.
* @param {number} gradeId Grade item ID where to navigate.
*/
gotoGrade(gradeId: number): void {
if (gradeId) {
this.navCtrl.push('CoreGradesGradePage', {courseId: this.courseId, userId: this.userId, gradeId: gradeId});
this.gradeId = gradeId;
let whereToPush, pageName;
if (this.svComponent) {
if (this.svComponent.getMasterNav().getActive().component.name == 'CoreGradesCourseSplitPage') {
// Table is on left side. Push on right.
whereToPush = this.svComponent;
pageName = 'CoreGradesGradePage';
} else {
// Table is on right side. Load new split view.
whereToPush = this.svComponent.getMasterNav();
pageName = 'CoreGradesCourseSplitPage';
}
} else {
if (this.appProvider.isWide()) {
// Table is full screen and large. Load here.
whereToPush = this.navCtrl;
pageName = 'CoreGradesCourseSplitPage';
} else {
// Table is full screen but on mobile. Load here.
whereToPush = this.navCtrl;
pageName = 'CoreGradesGradePage';
}
}
whereToPush.push(pageName, {courseId: this.courseId, userId: this.userId, gradeId: gradeId});
}
}
}

View File

@ -37,7 +37,7 @@ export class CoreGradesCoursesPage {
gradesLoaded = false;
constructor(private gradesProvider: CoreGradesProvider, private domUtils: CoreDomUtilsProvider,
private courseHelper: CoreGradesHelperProvider) {
private gradesHelper: CoreGradesHelperProvider) {
}
/**
@ -45,11 +45,10 @@ export class CoreGradesCoursesPage {
*/
ionViewDidLoad(): void {
if (this.courseId) {
// There is an event to load, open the event in a new state.
// There is the course to load, open the course in a new state.
this.gotoCourseGrades(this.courseId);
}
// Get first participants.
this.fetchData().then(() => {
if (!this.courseId && this.splitviewCtrl.isOn() && this.grades.length > 0) {
this.gotoCourseGrades(this.grades[0].courseid);
@ -69,7 +68,7 @@ export class CoreGradesCoursesPage {
*/
fetchData(): Promise<any> {
return this.gradesProvider.getCoursesGrades().then((grades) => {
return this.courseHelper.getGradesCourseData(grades).then((grades) => {
return this.gradesHelper.getGradesCourseData(grades).then((grades) => {
this.grades = grades;
});
}).catch((error) => {

View File

@ -0,0 +1,10 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.grades.grades' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<core-split-view>
<core-grades-course class="core-avoid-header" [courseId]="courseId" [userId]="userId" [gradeId]="gradeId"></core-grades-course>
</core-split-view>

View File

@ -0,0 +1,35 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreGradesCourseSplitPage } from './coursesplit';
import { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CoreGradesComponentsModule } from '../../components/components.module';
@NgModule({
declarations: [
CoreGradesCourseSplitPage
],
imports: [
CoreGradesComponentsModule,
CoreComponentsModule,
CoreDirectivesModule,
IonicPageModule.forChild(CoreGradesCourseSplitPage),
TranslateModule.forChild()
],
})
export class CoreGradesCourseSplitPageModule {}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 } from '@angular/core';
import { IonicPage, NavParams } from 'ionic-angular';
import { CoreSitesProvider } from '../../../../providers/sites';
/**
* Page that displays a course grades.
*/
@IonicPage({ segment: 'core-grades-course-split' })
@Component({
selector: 'page-core-grades-course-split',
templateUrl: 'coursesplit.html',
})
export class CoreGradesCourseSplitPage {
courseId: number;
userId: number;
gradeId: number;
constructor(navParams: NavParams, sitesProvider: CoreSitesProvider) {
this.courseId = navParams.get('courseId');
this.userId = navParams.get('userId') || sitesProvider.getCurrentSiteUserId();
this.gradeId = navParams.get('gradeId');
}
}

View File

@ -0,0 +1,72 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.grades.grade' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="gradeLoaded" (ionRefresh)="refreshGrade($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="gradeLoaded">
<core-empty-box *ngIf="!grade" icon="stats" [message]="errormessage"></core-empty-box>
<ion-list *ngIf="grade">
<a ion-item *ngIf="grade.itemname && grade.link" text-wrap detail-push [href]="grade.link" core-link captureLink="true">
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" item-start></ion-icon>
<img *ngIf="grade.image" [src]="grade.image" item-start/>
<h2><core-format-text [text]="grade.itemname"></core-format-text></h2>
</a>
<ion-item *ngIf="grade.itemname && !grade.link" text-wrap >
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" item-start></ion-icon>
<img *ngIf="grade.image" [src]="grade.image" item-start/>
<h2><core-format-text [text]="grade.itemname"></core-format-text></h2>
</ion-item>
<ion-item text-wrap *ngIf="grade.weight">
<h2>{{ 'core.grades.weight' | translate}}</h2>
<p><core-format-text [text]="grade.weight"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.grade">
<h2>{{ 'core.grades.grade' | translate}}</h2>
<p><core-format-text [text]="grade.grade"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.range">
<h2>{{ 'core.grades.range' | translate}}</h2>
<p><core-format-text [text]="grade.range"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.percentage">
<h2>{{ 'core.grades.percentage' | translate}}</h2>
<p><core-format-text [text]="grade.percentage"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.lettergrade">
<h2>{{ 'core.grades.lettergrade' | translate}}</h2>
<p><core-format-text [text]="grade.lettergrade"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.rank">
<h2>{{ 'core.grades.rank' | translate}}</h2>
<p><core-format-text [text]="grade.rank"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.average">
<h2>{{ 'core.grades.average' | translate}}</h2>
<p><core-format-text [text]="grade.average"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.feedback">
<h2>{{ 'core.grades.feedback' | translate}}</h2>
<p><core-format-text [fullTitle]="'core.grades.feedback' | translate" maxHeight="60" fullOnClick="true" [text]="grade.feedback"></core-format-text></p>
</ion-item>
<ion-item text-wrap *ngIf="grade.contributiontocoursetotal">
<h2>{{ 'core.grades.contributiontocoursetotal' | translate}}</h2>
<p><core-format-text [text]="grade.contributiontocoursetotal"></core-format-text></p>
</ion-item>
</ion-list>
</core-loading>
</ion-content>

View File

@ -0,0 +1,33 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreGradesGradePage } from './grade';
import { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
@NgModule({
declarations: [
CoreGradesGradePage
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
IonicPageModule.forChild(CoreGradesGradePage),
TranslateModule.forChild()
],
})
export class CoreGradesGradePageModule {}

View File

@ -0,0 +1,84 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, ViewChild } from '@angular/core';
import { IonicPage, Content, NavParams } from 'ionic-angular';
import { CoreGradesProvider } from '../../providers/grades';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { CoreGradesHelperProvider } from '../../providers/helper';
import { CoreSitesProvider } from '../../../../providers/sites';
/**
* Page that displays activity grade.
*/
@IonicPage({ segment: 'core-grades-grade' })
@Component({
selector: 'page-core-grades-grade',
templateUrl: 'grade.html',
})
export class CoreGradesGradePage {
@ViewChild(Content) content: Content;
grade: any;
courseId: number;
userId: number;
gradeId: number;
errormessage: string;
gradeLoaded = false;
constructor(private gradesProvider: CoreGradesProvider, private domUtils: CoreDomUtilsProvider,
private gradesHelper: CoreGradesHelperProvider, navParams: NavParams, sitesProvider: CoreSitesProvider) {
this.courseId = navParams.get('courseId');
this.userId = navParams.get('userId') || sitesProvider.getCurrentSiteUserId();
this.gradeId = navParams.get('gradeId');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchData().finally(() => {
this.gradeLoaded = true;
});
}
/**
* Fetch all the data required for the view.
*
* @return {Promise<any>} Resolved when done.
*/
fetchData(): Promise<any> {
return this.gradesHelper.getGradeItem(this.courseId, this.gradeId, this.userId).then((grade) => {
this.grade = grade;
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error loading grade item');
this.errormessage = error || 'Grade not found';
});
}
/**
* Refresh data.
*
* @param {any} refresher Refresher.
*/
refreshGrade(refresher: any): void {
this.gradesProvider.invalidateCourseGradesData(this.courseId, this.userId).finally(() => {
this.fetchData().finally(() => {
refresher.complete();
});
});
}
}

View File

@ -45,7 +45,7 @@ export class CoreGradesProvider {
}
/**
* Get cache key for grade table data WS calls.
* Get cache key for grade items data WS calls.
*
* @param {number} courseId ID of the course to get the grades from.
* @param {number} userId ID of the user to get the grades from.
@ -77,6 +77,37 @@ export class CoreGradesProvider {
return this.ROOT_CACHE_KEY + 'coursesgrades';
}
/**
* Get the grade items for a certain module. Keep in mind that may have more than one item to include outcomes and scales.
* Fallback function only used if 'gradereport_user_get_grade_items' WS is not avalaible Moodle < 3.2.
*
* @param {number} courseId ID of the course to get the grades from.
* @param {number} [userId] ID of the user to get the grades from. If not defined use site's current user.
* @param {number} [groupId] ID of the group to get the grades from. Not used for old gradebook table.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise to be resolved when the grades are retrieved.
*/
getGradeItems(courseId: number, userId?: number, groupId?: number, siteId?: string, ignoreCache: boolean = false):
Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return this.isGradeItemsAvalaible(siteId).then((enabled) => {
if (enabled) {
return this.getCourseGradesItems(courseId, userId, groupId, siteId, ignoreCache).catch(() => {
// FallBack while solving MDL-57255.
return this.getCourseGradesTable(courseId, userId, siteId, ignoreCache);
});
} else {
return this.getCourseGradesTable(courseId, userId, siteId, ignoreCache);
}
});
});
}
/**
* Get the grade items for a certain course.
*
@ -210,6 +241,21 @@ export class CoreGradesProvider {
});
}
/**
* Invalidates courses grade items data WS calls.
*
* @param {number} courseId ID of the course to get the grades from.
* @param {number} userId ID of the user to get the grades from.
* @param {number} [groupId] ID of the group to get the grades from. Default: 0.
* @param {string} [siteId] Site id (empty for current site).
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateCourseGradesItemsData(courseId: number, userId: number, groupId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId));
});
}
/**
* Returns whether or not the plugin is enabled for a certain site.
*

View File

@ -20,6 +20,7 @@ import { CoreCoursesProvider } from '../../courses/providers/courses';
import { CoreCourseProvider } from '../../course/providers/course';
import { CoreGradesProvider } from './grades';
import { CoreTextUtilsProvider } from '../../../providers/utils/text';
import { CoreUrlUtilsProvider } from '../../../providers/utils/url';
import { CoreDomUtilsProvider } from '../../../providers/utils/dom';
/**
@ -32,10 +33,103 @@ export class CoreGradesHelperProvider {
constructor(logger: CoreLoggerProvider, private coursesProvider: CoreCoursesProvider,
private gradesProvider: CoreGradesProvider, private sitesProvider: CoreSitesProvider,
private textUtils: CoreTextUtilsProvider, private courseProvider: CoreCourseProvider,
private domUtils: CoreDomUtilsProvider, private translate: TranslateService) {
private domUtils: CoreDomUtilsProvider, private translate: TranslateService,
private urlUtils: CoreUrlUtilsProvider) {
this.logger = logger.getInstance('CoreGradesHelperProvider');
}
/**
* Formats a row from the grades table te be rendered in a page.
*
* @param {any} tableRow JSON object representing row of grades table data.
* @return {any} Formatted row object.
*/
protected formatGradeRow(tableRow: any): any {
const row = {};
for (const name in tableRow) {
if (typeof(tableRow[name].content) != 'undefined') {
let content = tableRow[name].content;
if (name == 'itemname') {
this.setRowIcon(row, content);
row['link'] = this.getModuleLink(content);
row['rowclass'] += tableRow[name].class.indexOf('hidden') >= 0 ? ' hidden' : '';
row['rowclass'] += tableRow[name].class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
content = content.replace(/<\/span>/gi, '\n');
content = this.textUtils.cleanTags(content);
} else {
content = this.textUtils.replaceNewLines(content, '<br>');
}
if (content == '&nbsp;') {
content = '';
}
row[name] = content.trim();
}
}
return row;
}
/**
* Formats a row from the grades table to be rendered in one table.
*
* @param {any} tableRow JSON object representing row of grades table data.
* @return {any} Formatted row object.
*/
protected formatGradeRowForTable(tableRow: any): any {
const row = {};
for (let name in tableRow) {
if (typeof(tableRow[name].content) != 'undefined') {
let content = tableRow[name].content;
if (name == 'itemname') {
this.setRowIcon(row, content);
row['rowclass'] = tableRow[name].class.indexOf('leveleven') < 0 ? 'odd' : 'even';
row['rowclass'] += tableRow[name].class.indexOf('hidden') >= 0 ? ' hidden' : '';
row['rowclass'] += tableRow[name].class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
content = content.replace(/<\/span>/gi, '\n');
content = this.textUtils.cleanTags(content);
row['id'] = parseInt(tableRow[name].id.split('_')[1], 10);
row['colspan'] = tableRow[name].colspan;
row['rowspan'] = (tableRow['leader'] && tableRow['leader'].rowspan) || 1;
name = 'gradeitem';
} else {
content = this.textUtils.replaceNewLines(content, '<br>');
}
if (content == '&nbsp;') {
content = '';
}
row[name] = content.trim();
}
}
return row;
}
/**
* Removes suffix formatted to compatibilize data from table and items.
*
* @param {any} item Grade item to format.
* @return {any} Grade item formatted.
*/
protected formatGradeItem(item: any): any {
for (const name in item) {
let index = name.indexOf('formatted');
if (index > 0) {
item[name.substr(0, index)] = item[name];
}
}
return item;
}
/**
* Formats the response of gradereport_user_get_grades_table to be rendered.
*
@ -62,7 +156,7 @@ export class CoreGradesHelperProvider {
contributiontocoursetotal: false
};
formatted.rows = table.tabledata.map((row: any) => {
return this.getGradeRow(row);
return this.formatGradeRowForTable(row);
}).filter((row: any) => {
return typeof row.gradeitem !== 'undefined';
});
@ -97,47 +191,6 @@ export class CoreGradesHelperProvider {
return formatted;
}
/**
* Get a row from the grades table.
*
* @param {any} tableRow JSON object representing row of grades table data.
* @return {any} Formatted row object.
*/
getGradeRow(tableRow: any): any {
const row = {};
for (let name in tableRow) {
if (typeof(tableRow[name].content) != 'undefined') {
let content = tableRow[name].content;
if (name == 'itemname') {
this.setRowIcon(row, content);
row['link'] = this.getModuleLink(content);
row['rowclass'] = tableRow[name].class.indexOf('leveleven') < 0 ? 'odd' : 'even';
row['rowclass'] += tableRow[name].class.indexOf('hidden') >= 0 ? ' hidden' : '';
row['rowclass'] += tableRow[name].class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
content = content.replace(/<\/span>/gi, '\n');
content = this.textUtils.cleanTags(content);
row['id'] = parseInt(tableRow[name].id.split('_')[1], 10);
row['colspan'] = tableRow[name].colspan;
row['rowspan'] = (tableRow['leader'] && tableRow['leader'].rowspan) || 1;
name = 'gradeitem';
} else {
content = this.textUtils.replaceNewLines(content, '<br>');
}
if (content == '&nbsp;') {
content = '';
}
row[name] = content.trim();
}
}
return row;
}
/**
* Get course data for grades since they only have courseid.
*
@ -162,6 +215,156 @@ export class CoreGradesHelperProvider {
});
}
/**
* Get an specific grade item.
*
* @param {number} courseId ID of the course to get the grades from.
* @param {number} gradeId Grade ID.
* @param {number} [userId] ID of the user to get the grades from. If not defined use site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise to be resolved when the grades are retrieved.
*/
getGradeItem(courseId: number, gradeId: number, userId?: number, siteId?: string, ignoreCache: boolean = false): Promise<any> {
return this.gradesProvider.getCourseGradesTable(courseId, userId, siteId, ignoreCache).then((grades) => {
if (grades) {
return this.getGradesTableRow(grades, gradeId);
}
return Promise.reject(null);
});
}
/**
* Get the grade items for a certain module. Keep in mind that may have more than one item to include outcomes and scales.
*
* @param {number} courseId ID of the course to get the grades from.
* @param {number} moduleId Module ID.
* @param {number} [userId] ID of the user to get the grades from. If not defined use site's current user.
* @param {number} [groupId] ID of the group to get the grades from. Not used for old gradebook table.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise to be resolved when the grades are retrieved.
*/
getGradeModuleItems(courseId: number, moduleId: number, userId?: number, groupId?: number, siteId?: string,
ignoreCache: boolean = false): Promise<any> {
return this.gradesProvider.getGradeItems(courseId, userId, groupId, siteId, ignoreCache).then((grades) => {
if (grades) {
if (typeof grades.tabledata != 'undefined') {
// Table format.
return this.getModuleGradesTableRows(grades, moduleId);
} else {
return grades.filter((item) => {
return item.cmid == moduleId;
}).map((item) => {
return this.formatGradeItem(item);
});
}
}
return Promise.reject(null);
});
}
/**
* Gets the link to the module for the selected grade.
*
* @param {string} text HTML where the link is present.
* @return {string | false} URL linking to the module.
*/
protected getModuleLink(text: string): string | false {
const el = this.domUtils.toDom(text)[0],
link = el.attributes['href'] ? el.attributes['href'].value : false;
if (!link || link.indexOf('/mod/') < 0) {
return false;
}
return link;
}
/**
* Get a row from the grades table.
*
* @param {any} table JSON object representing a table with data.
* @param {number} gradeId Grade Object identifier.
* @return {any} Formatted HTML table.
*/
getGradesTableRow(table: any, gradeId: number): any {
if (table.tabledata) {
const selectedRow = table.tabledata.find((row) => {
return row.itemname && row.itemname.id && row.itemname.id.substr(0, 3) == 'row' &&
parseInt(row.itemname.id.split('_')[1], 10) == gradeId;
});
if (selectedRow) {
return this.formatGradeRow(selectedRow);
}
}
return '';
}
/**
* Get the rows related to a module from the grades table.
*
* @param {any} table JSON object representing a table with data.
* @param {number} moduleId Grade Object identifier.
* @return {any} Formatted HTML table.
*/
getModuleGradesTableRows(table: any, moduleId: number): any {
if (table.tabledata) {
// Find href containing "/mod/xxx/xxx.php".
const regex = /href="([^"]*\/mod\/[^"|^\/]*\/[^"|^\.]*\.php[^"]*)/;
return table.tabledata.filter((row) => {
if (row.itemname && row.itemname.content) {
const matches = row.itemname.content.match(regex);
if (matches && matches.length) {
const hrefParams = this.urlUtils.extractUrlParams(matches[1]);
return hrefParams && hrefParams.id == moduleId;
}
}
return false;
}).map((row) => {
return this.formatGradeRow(row);
});
}
return [];
}
/**
* Invalidate the grade items for a certain module.
*
* @param {number} courseId ID of the course to invalidate the grades.
* @param {number} [userId] ID of the user to invalidate. If not defined use site's current user.
* @param {number} [groupId] ID of the group to invalidate. Not used for old gradebook table.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise} Promise to be resolved when the grades are invalidated.
*/
invalidateGradeModuleItems(courseId: number, userId?: number, groupId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return this.gradesProvider.isGradeItemsAvalaible(siteId).then((enabled) => {
if (enabled) {
return this.gradesProvider.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId);
} else {
return this.gradesProvider.invalidateCourseGradesData(courseId, userId, siteId);
}
});
});
}
/**
* Parses the image and sets it to the row.
*
@ -201,21 +404,4 @@ export class CoreGradesHelperProvider {
return row;
}
/**
* Gets the link to the module for the selected grade.
*
* @param {string} text HTML where the link is present.
* @return {string | false} URL linking to the module.
*/
protected getModuleLink(text: string): string | false {
const el = this.domUtils.toDom(text)[0],
link = el.attributes['href'] ? el.attributes['href'].value : false;
if (!link || link.indexOf('/mod/') < 0) {
return false;
}
return link;
}
}

View File

@ -191,6 +191,15 @@ export class CoreAppProvider {
return this.platform.is('cordova');
}
/**
* Checks if the current window is wider than a mobile.
*
* @return {boolean} Whether the app the current window is wider than a mobile.
*/
isWide(): boolean {
return this.platform.width() > 768;
}
/**
* Returns whether we are online.
*