diff --git a/src/app/app.scss b/src/app/app.scss index 30520d667..e21adbc2e 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -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; } } diff --git a/src/core/grades/components/course/course.html b/src/core/grades/components/course/course.html index 033744660..ca54f693d 100644 --- a/src/core/grades/components/course/course.html +++ b/src/core/grades/components/course/course.html @@ -19,7 +19,7 @@ - + diff --git a/src/core/grades/components/course/course.scss b/src/core/grades/components/course/course.scss index b02ce767b..a04232fb8 100644 --- a/src/core/grades/components/course/course.scss +++ b/src/core/grades/components/course/course.scss @@ -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 { } } } -} \ No newline at end of file +} + +.split-pane-side, .split-pane-main { + core-grades-course .core-grades-table .hidden-phone { + display: none; + opacity: 0; + } +} diff --git a/src/core/grades/components/course/course.ts b/src/core/grades/components/course/course.ts index 0985d25e6..5913ac22c 100644 --- a/src/core/grades/components/course/course.ts +++ b/src/core/grades/components/course/course.ts @@ -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}); } } } diff --git a/src/core/grades/pages/courses/courses.ts b/src/core/grades/pages/courses/courses.ts index 4dd345cff..fd54a58d8 100644 --- a/src/core/grades/pages/courses/courses.ts +++ b/src/core/grades/pages/courses/courses.ts @@ -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 { 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) => { diff --git a/src/core/grades/pages/coursesplit/coursesplit.html b/src/core/grades/pages/coursesplit/coursesplit.html new file mode 100644 index 000000000..de279c1f7 --- /dev/null +++ b/src/core/grades/pages/coursesplit/coursesplit.html @@ -0,0 +1,10 @@ + + + {{ 'core.grades.grades' | translate }} + + + + + + + diff --git a/src/core/grades/pages/coursesplit/coursesplit.module.ts b/src/core/grades/pages/coursesplit/coursesplit.module.ts new file mode 100644 index 000000000..57b081e99 --- /dev/null +++ b/src/core/grades/pages/coursesplit/coursesplit.module.ts @@ -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 {} diff --git a/src/core/grades/pages/coursesplit/coursesplit.ts b/src/core/grades/pages/coursesplit/coursesplit.ts new file mode 100644 index 000000000..f759b72ca --- /dev/null +++ b/src/core/grades/pages/coursesplit/coursesplit.ts @@ -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'); + } +} diff --git a/src/core/grades/pages/grade/grade.html b/src/core/grades/pages/grade/grade.html new file mode 100644 index 000000000..5f9268c9c --- /dev/null +++ b/src/core/grades/pages/grade/grade.html @@ -0,0 +1,72 @@ + + + {{ 'core.grades.grade' | translate }} + + + + + + + + + + + + + +

+
+ + + + +

+
+ + +

{{ 'core.grades.weight' | translate}}

+

+
+ + +

{{ 'core.grades.grade' | translate}}

+

+
+ + +

{{ 'core.grades.range' | translate}}

+

+
+ + +

{{ 'core.grades.percentage' | translate}}

+

+
+ + +

{{ 'core.grades.lettergrade' | translate}}

+

+
+ + +

{{ 'core.grades.rank' | translate}}

+

+
+ + +

{{ 'core.grades.average' | translate}}

+

+
+ + +

{{ 'core.grades.feedback' | translate}}

+

+
+ + +

{{ 'core.grades.contributiontocoursetotal' | translate}}

+

+
+
+
+
diff --git a/src/core/grades/pages/grade/grade.module.ts b/src/core/grades/pages/grade/grade.module.ts new file mode 100644 index 000000000..5b591fddd --- /dev/null +++ b/src/core/grades/pages/grade/grade.module.ts @@ -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 {} diff --git a/src/core/grades/pages/grade/grade.ts b/src/core/grades/pages/grade/grade.ts new file mode 100644 index 000000000..8ef99c85d --- /dev/null +++ b/src/core/grades/pages/grade/grade.ts @@ -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} Resolved when done. + */ + fetchData(): Promise { + 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(); + }); + }); + } +} diff --git a/src/core/grades/providers/grades.ts b/src/core/grades/providers/grades.ts index 6b9f833aa..f4efc010b 100644 --- a/src/core/grades/providers/grades.ts +++ b/src/core/grades/providers/grades.ts @@ -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} Promise to be resolved when the grades are retrieved. + */ + getGradeItems(courseId: number, userId?: number, groupId?: number, siteId?: string, ignoreCache: boolean = false): + Promise { + 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} Promise resolved when the data is invalidated. + */ + invalidateCourseGradesItemsData(courseId: number, userId: number, groupId: number, siteId?: string): Promise { + 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. * diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index a5e62a4d8..f0cb2d0f0 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -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, '
'); + } + + if (content == ' ') { + 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, '
'); + } + + if (content == ' ') { + 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, '
'); - } - - if (content == ' ') { - 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} Promise to be resolved when the grades are retrieved. + */ + getGradeItem(courseId: number, gradeId: number, userId?: number, siteId?: string, ignoreCache: boolean = false): Promise { + + 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} Promise to be resolved when the grades are retrieved. + */ + getGradeModuleItems(courseId: number, moduleId: number, userId?: number, groupId?: number, siteId?: string, + ignoreCache: boolean = false): Promise { + + 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 { + 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; - } } diff --git a/src/providers/app.ts b/src/providers/app.ts index 86df6ce5d..054ef2467 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -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. *