MOBILE-2310 course: Implement topics and weeks formats
parent
5aef9af45b
commit
e1bc11e44b
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
export class CoreConstants {
|
export class CoreConstants {
|
||||||
public static secondsYear = 31536000;
|
public static secondsYear = 31536000;
|
||||||
|
public static secondsWeek = 604800;
|
||||||
public static secondsDay = 86400;
|
public static secondsDay = 86400;
|
||||||
public static secondsHour = 3600;
|
public static secondsHour = 3600;
|
||||||
public static secondsMinute = 60;
|
public static secondsMinute = 60;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- Default course format. -->
|
<!-- Default course format. -->
|
||||||
<div *ngIf="!componentInstances.courseFormat">
|
<div *ngIf="!componentInstances.courseFormat">
|
||||||
<!-- Course summary. -->
|
<!-- Course summary. By default we only display the course progress. -->
|
||||||
<ion-list no-lines *ngIf="!componentInstances.courseSummary">
|
<ion-list no-lines *ngIf="!componentInstances.courseSummary">
|
||||||
<ion-item *ngIf="course.progress !== false">
|
<ion-item *ngIf="course.progress !== false">
|
||||||
<core-progress-bar [progress]="course.progress"></core-progress-bar>
|
<core-progress-bar [progress]="course.progress"></core-progress-bar>
|
||||||
|
@ -8,39 +8,41 @@
|
||||||
</ion-list>
|
</ion-list>
|
||||||
<ng-template #courseSummary></ng-template>
|
<ng-template #courseSummary></ng-template>
|
||||||
|
|
||||||
<!-- Section selector. -->
|
<core-loading [hideUntil]="loaded">
|
||||||
<ion-row *ngIf="!componentInstances.sectionSelector && displaySectionSelector && sections && sections.length">
|
<!-- Section selector. -->
|
||||||
<ion-col col-11>
|
<ion-row *ngIf="!componentInstances.sectionSelector && displaySectionSelector && sections && sections.length">
|
||||||
<ion-item>
|
<ion-col col-11>
|
||||||
<ion-select [ngModel]="selectedSection" (ngModelChange)="sectionChanged($event)" [compareWith]="compareSections" [selectOptions]="selectOptions">
|
<ion-item>
|
||||||
<ion-option *ngFor="let section of sections" [value]="section">{{section.formattedName || section.name}}</ion-option>
|
<ion-select [ngModel]="selectedSection" (ngModelChange)="sectionChanged($event)" [compareWith]="compareSections" [selectOptions]="selectOptions">
|
||||||
</ion-select>
|
<ion-option *ngFor="let section of sections" [value]="section">{{section.formattedName || section.name}}</ion-option>
|
||||||
</ion-item>
|
</ion-select>
|
||||||
</ion-col>
|
</ion-item>
|
||||||
<ion-col col-1 class="text-right">
|
</ion-col>
|
||||||
<!-- @todo Download button. -->
|
<ion-col col-1 class="text-right">
|
||||||
</ion-col>
|
<!-- @todo Download button. -->
|
||||||
</ion-row>
|
</ion-col>
|
||||||
<ng-template #sectionSelector></ng-template>
|
</ion-row>
|
||||||
|
<ng-template #sectionSelector></ng-template>
|
||||||
|
|
||||||
<!-- Single section. -->
|
<!-- Single section. -->
|
||||||
<div *ngIf="selectedSection && selectedSection.id != allSectionsId">
|
<div *ngIf="selectedSection && selectedSection.id != allSectionsId">
|
||||||
<ng-container *ngIf="!componentInstances.singleSection">
|
<ng-container *ngIf="!componentInstances.singleSection">
|
||||||
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container>
|
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container>
|
||||||
<core-empty-box *ngIf="!selectedSection.hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
<core-empty-box *ngIf="!selectedSection.hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
||||||
</ng-container>
|
|
||||||
<ng-template #singleSection></ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Multiple sections. -->
|
|
||||||
<div *ngIf="selectedSection && selectedSection.id == allSectionsId">
|
|
||||||
<ng-container *ngIf="!componentInstances.allSections">
|
|
||||||
<ng-container *ngFor="let section of sections">
|
|
||||||
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
<ng-template #singleSection></ng-template>
|
||||||
<ng-template #allSections></ng-template>
|
</div>
|
||||||
</div>
|
|
||||||
|
<!-- Multiple sections. -->
|
||||||
|
<div *ngIf="selectedSection && selectedSection.id == allSectionsId">
|
||||||
|
<ng-container *ngIf="!componentInstances.allSections">
|
||||||
|
<ng-container *ngFor="let section of sections">
|
||||||
|
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #allSections></ng-template>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #sectionTemplate let-section="section">
|
<ng-template #sectionTemplate let-section="section">
|
||||||
|
|
|
@ -68,6 +68,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges {
|
||||||
selectedSection: any;
|
selectedSection: any;
|
||||||
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
|
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
|
||||||
selectOptions: any = {};
|
selectOptions: any = {};
|
||||||
|
loaded: boolean;
|
||||||
|
|
||||||
protected logger;
|
protected logger;
|
||||||
|
|
||||||
|
@ -95,7 +96,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges {
|
||||||
if (changes.sections && this.sections) {
|
if (changes.sections && this.sections) {
|
||||||
if (!this.selectedSection) {
|
if (!this.selectedSection) {
|
||||||
// There is no selected section yet, calculate which one to get.
|
// There is no selected section yet, calculate which one to get.
|
||||||
this.sectionChanged(this.cfDelegate.getCurrentSection(this.course, this.sections));
|
this.cfDelegate.getCurrentSection(this.course, this.sections).then((section) => {
|
||||||
|
this.loaded = true;
|
||||||
|
this.sectionChanged(section);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// We have a selected section, but the list has changed. Search the section in the list.
|
// We have a selected section, but the list has changed. Search the section in the list.
|
||||||
let newSection;
|
let newSection;
|
||||||
|
|
|
@ -17,16 +17,22 @@ import { CoreCourseProvider } from './providers/course';
|
||||||
import { CoreCourseHelperProvider } from './providers/helper';
|
import { CoreCourseHelperProvider } from './providers/helper';
|
||||||
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
||||||
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
||||||
|
import { CoreCourseFormatDefaultHandler } from './providers/default-format';
|
||||||
|
import { CoreCourseFormatTopicsModule} from './formats/topics/topics.module';
|
||||||
|
import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [],
|
declarations: [],
|
||||||
imports: [
|
imports: [
|
||||||
|
CoreCourseFormatTopicsModule,
|
||||||
|
CoreCourseFormatWeeksModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CoreCourseProvider,
|
CoreCourseProvider,
|
||||||
CoreCourseHelperProvider,
|
CoreCourseHelperProvider,
|
||||||
CoreCourseFormatDelegate,
|
CoreCourseFormatDelegate,
|
||||||
CoreCourseModuleDelegate
|
CoreCourseModuleDelegate,
|
||||||
|
CoreCourseFormatDefaultHandler
|
||||||
],
|
],
|
||||||
exports: []
|
exports: []
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreCourseFormatHandler } from '../../../providers/format-delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support topics course format.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseFormatTopicsHandler implements CoreCourseFormatHandler {
|
||||||
|
name = 'topics';
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||||
|
*/
|
||||||
|
isEnabled() : boolean|Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// (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 { CoreCourseFormatTopicsHandler } from './providers/handler';
|
||||||
|
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
imports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CoreCourseFormatTopicsHandler
|
||||||
|
],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class CoreCourseFormatTopicsModule {
|
||||||
|
constructor(formatDelegate: CoreCourseFormatDelegate, handler: CoreCourseFormatTopicsHandler) {
|
||||||
|
formatDelegate.registerHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreCourseFormatHandler } from '../../../providers/format-delegate';
|
||||||
|
import { CoreTimeUtilsProvider } from '../../../../../providers/utils/time';
|
||||||
|
import { CoreConstants } from '../../../../constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support weeks course format.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler {
|
||||||
|
name = 'weeks';
|
||||||
|
|
||||||
|
constructor(private timeUtils: CoreTimeUtilsProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||||
|
*/
|
||||||
|
isEnabled() : boolean|Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of sections, get the "current" section that should be displayed first.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to get the title.
|
||||||
|
* @param {any[]} sections List of sections.
|
||||||
|
* @return {any|Promise<any>} Current section (or promise resolved with current section).
|
||||||
|
*/
|
||||||
|
getCurrentSection(course: any, sections: any[]) : any|Promise<any> {
|
||||||
|
let now = this.timeUtils.timestamp();
|
||||||
|
|
||||||
|
if (now < course.startdate || (course.enddate && now > course.enddate)) {
|
||||||
|
// Course hasn't started yet or it has ended already. Return the first section.
|
||||||
|
return sections[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < sections.length; i++) {
|
||||||
|
let section = sections[i];
|
||||||
|
if (typeof section.section == 'undefined' || section.section < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dates = this.getSectionDates(section, course.startdate);
|
||||||
|
if ((now >= dates.start) && (now < dates.end)) {
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The section wasn't found, return the first section.
|
||||||
|
return sections[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the start and end date of a section.
|
||||||
|
*
|
||||||
|
* @param {any} section The section to treat.
|
||||||
|
* @param {number} startDate The course start date (in seconds).
|
||||||
|
* @return {{start: number, end: number}} An object with the start and end date of the section.
|
||||||
|
*/
|
||||||
|
protected getSectionDates(section: any, startDate: number) : {start: number, end: number} {
|
||||||
|
// Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight savings and the date changes).
|
||||||
|
startDate = startDate + 7200;
|
||||||
|
|
||||||
|
let dates = {
|
||||||
|
start: startDate + (CoreConstants.secondsWeek * (section.section - 1)),
|
||||||
|
end: 0
|
||||||
|
};
|
||||||
|
dates.end = dates.start + CoreConstants.secondsWeek;
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// (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 { CoreCourseFormatWeeksHandler } from './providers/handler';
|
||||||
|
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
imports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CoreCourseFormatWeeksHandler
|
||||||
|
],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class CoreCourseFormatWeeksModule {
|
||||||
|
constructor(formatDelegate: CoreCourseFormatDelegate, handler: CoreCourseFormatWeeksHandler) {
|
||||||
|
formatDelegate.registerHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -157,6 +157,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
|
|
||||||
promises.push(this.courseProvider.invalidateSections(this.course.id));
|
promises.push(this.courseProvider.invalidateSections(this.course.id));
|
||||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||||
|
promises.push(this.courseFormatDelegate.invalidateData(this.course, this.sections));
|
||||||
|
|
||||||
// if ($scope.sections) {
|
// if ($scope.sections) {
|
||||||
// promises.push($mmCoursePrefetchDelegate.invalidateCourseUpdates(courseId));
|
// promises.push($mmCoursePrefetchDelegate.invalidateCourseUpdates(courseId));
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
import { CoreCoursesProvider } from '../../courses/providers/courses';
|
||||||
|
import { CoreCourseFormatHandler } from './format-delegate';
|
||||||
|
import { CoreCourseProvider } from './course';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default handler used when the course format doesn't have a specific implementation.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
|
name = 'default';
|
||||||
|
|
||||||
|
constructor(private coursesProvider: CoreCoursesProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||||
|
*/
|
||||||
|
isEnabled() : boolean|Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title to use in course page.
|
||||||
|
*
|
||||||
|
* @param {any} course The course.
|
||||||
|
* @return {string} Title.
|
||||||
|
*/
|
||||||
|
getCourseTitle?(course: any) : string {
|
||||||
|
return course.fullname || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether it allows seeing all sections at the same time. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to check.
|
||||||
|
* @type {boolean} Whether it can view all sections.
|
||||||
|
*/
|
||||||
|
canViewAllSections(course: any) : boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the default section selector should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to check.
|
||||||
|
* @type {boolean} Whether the default section selector should be displayed.
|
||||||
|
*/
|
||||||
|
displaySectionSelector(course: any) : boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of sections, get the "current" section that should be displayed first.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to get the title.
|
||||||
|
* @param {any[]} sections List of sections.
|
||||||
|
* @return {any|Promise<any>} Current section (or promise resolved with current section).
|
||||||
|
*/
|
||||||
|
getCurrentSection(course: any, sections: any[]) : any|Promise<any> {
|
||||||
|
// We need the "marker" to determine the current section.
|
||||||
|
return this.coursesProvider.getCoursesByField('id', course.id).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
}).then((courses) => {
|
||||||
|
if (courses && courses[0]) {
|
||||||
|
// Find the marked section.
|
||||||
|
let course = courses[0];
|
||||||
|
for (let i = 0; i < sections.length; i++) {
|
||||||
|
let section = sections[i];
|
||||||
|
if (section.section == course.marker) {
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marked section not found or we couldn't retrieve the marker. Return the first section.
|
||||||
|
for (let i = 0; i < sections.length; i++) {
|
||||||
|
let section = sections[i];
|
||||||
|
if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the data required to load the course format.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to get the title.
|
||||||
|
* @param {any[]} sections List of sections.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateData(course: any, sections: any[]) : Promise<any> {
|
||||||
|
return this.coursesProvider.invalidateCoursesByField('id', course.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
|
||||||
|
* Implement it only if you want to create your own page to display the course. In general it's better to use the method
|
||||||
|
* getCourseFormatComponent because it will display the course handlers at the top.
|
||||||
|
* Your page should include the course handlers using CoreCoursesDelegate.
|
||||||
|
*
|
||||||
|
* @param {NavController} navCtrl The NavController instance to use.
|
||||||
|
* @param {any} course The course to open. It should contain a "format" attribute.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
openCourse(navCtrl: NavController, course: any) : Promise<any> {
|
||||||
|
return navCtrl.push('CoreCourseSectionPage', {course: course});
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import { CoreEventsProvider } from '../../../providers/events';
|
||||||
import { CoreLoggerProvider } from '../../../providers/logger';
|
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||||
import { CoreSitesProvider } from '../../../providers/sites';
|
import { CoreSitesProvider } from '../../../providers/sites';
|
||||||
import { CoreCourseProvider } from './course';
|
import { CoreCourseProvider } from './course';
|
||||||
|
import { CoreCourseFormatDefaultHandler } from './default-format';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that all course format handlers should implement.
|
* Interface that all course format handlers should implement.
|
||||||
|
@ -65,9 +66,10 @@ export interface CoreCourseFormatHandler {
|
||||||
*
|
*
|
||||||
* @param {any} course The course to get the title.
|
* @param {any} course The course to get the title.
|
||||||
* @param {any[]} sections List of sections.
|
* @param {any[]} sections List of sections.
|
||||||
* @return {any} Current section.
|
* @return {any|Promise<any>} Current section (or promise resolved with current section). If a promise is returned, it should
|
||||||
|
* never fail.
|
||||||
*/
|
*/
|
||||||
getCurrentSection?(course: any, sections: any[]) : any;
|
getCurrentSection?(course: any, sections: any[]) : any|Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
|
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
|
||||||
|
@ -123,6 +125,15 @@ export interface CoreCourseFormatHandler {
|
||||||
* @return {any} The component to use, undefined if not found.
|
* @return {any} The component to use, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getAllSectionsComponent?(course: any): any;
|
getAllSectionsComponent?(course: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the data required to load the course format.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to get the title.
|
||||||
|
* @param {any[]} sections List of sections.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateData?(course: any, sections: any[]) : Promise<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,7 +146,8 @@ export class CoreCourseFormatDelegate {
|
||||||
protected enabledHandlers: {[s: string]: CoreCourseFormatHandler} = {}; // Handlers enabled for the current site.
|
protected enabledHandlers: {[s: string]: CoreCourseFormatHandler} = {}; // Handlers enabled for the current site.
|
||||||
protected lastUpdateHandlersStart: number;
|
protected lastUpdateHandlersStart: number;
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider) {
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
|
||||||
|
private defaultHandler: CoreCourseFormatDefaultHandler) {
|
||||||
this.logger = logger.getInstance('CoreCoursesCourseFormatDelegate');
|
this.logger = logger.getInstance('CoreCoursesCourseFormatDelegate');
|
||||||
|
|
||||||
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
||||||
|
@ -150,7 +162,7 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {boolean} Whether it allows seeing all sections at the same time.
|
* @return {boolean} Whether it allows seeing all sections at the same time.
|
||||||
*/
|
*/
|
||||||
canViewAllSections(course: any) : boolean {
|
canViewAllSections(course: any) : boolean {
|
||||||
return this.executeFunction(course.format, 'canViewAllSections', true, [course]);
|
return this.executeFunction(course.format, 'canViewAllSections', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,25 +172,25 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {boolean} Whether the section selector should be displayed.
|
* @return {boolean} Whether the section selector should be displayed.
|
||||||
*/
|
*/
|
||||||
displaySectionSelector(course: any) : boolean {
|
displaySectionSelector(course: any) : boolean {
|
||||||
return this.executeFunction(course.format, 'displaySectionSelector', true, [course]);
|
return this.executeFunction(course.format, 'displaySectionSelector', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a certain function in a course format handler. If the handler isn't found or function isn't defined,
|
* Execute a certain function in a course format handler.
|
||||||
* return the default value.
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||||
*
|
*
|
||||||
* @param {string} format The format name.
|
* @param {string} format The format name.
|
||||||
* @param {string} fnName Name of the function to execute.
|
* @param {string} fnName Name of the function to execute.
|
||||||
* @param {any} defaultValue Value to return if not found.
|
|
||||||
* @param {any[]} params Parameters to pass to the function.
|
* @param {any[]} params Parameters to pass to the function.
|
||||||
* @return {any} Function returned value or default value.
|
* @return {any} Function returned value or default value.
|
||||||
*/
|
*/
|
||||||
protected executeFunction(format: string, fnName: string, defaultValue?: any, params?: any[]) : any {
|
protected executeFunction(format: string, fnName: string, params?: any[]) : any {
|
||||||
let handler = this.enabledHandlers[format];
|
let handler = this.enabledHandlers[format];
|
||||||
if (handler && handler[fnName]) {
|
if (handler && handler[fnName]) {
|
||||||
return handler[fnName].apply(handler, params);
|
return handler[fnName].apply(handler, params);
|
||||||
|
} else if (this.defaultHandler[fnName]) {
|
||||||
|
return this.defaultHandler[fnName].apply(this.defaultHandler, params);
|
||||||
}
|
}
|
||||||
return defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,7 +200,7 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {any} The component to use, undefined if not found.
|
* @return {any} The component to use, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getAllSectionsComponent(course: any) : any {
|
getAllSectionsComponent(course: any) : any {
|
||||||
return this.executeFunction(course.format, 'getAllSectionsComponent', undefined, [course]);
|
return this.executeFunction(course.format, 'getAllSectionsComponent', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -198,7 +210,7 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {any} The component to use, undefined if not found.
|
* @return {any} The component to use, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getCourseFormatComponent(course: any) : any {
|
getCourseFormatComponent(course: any) : any {
|
||||||
return this.executeFunction(course.format, 'getCourseFormatComponent', undefined, [course]);
|
return this.executeFunction(course.format, 'getCourseFormatComponent', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +220,7 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {any} The component to use, undefined if not found.
|
* @return {any} The component to use, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getCourseSummaryComponent(course: any) : any {
|
getCourseSummaryComponent(course: any) : any {
|
||||||
return this.executeFunction(course.format, 'getCourseSummaryComponent', undefined, [course]);
|
return this.executeFunction(course.format, 'getCourseSummaryComponent', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,7 +230,7 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {string} Course title.
|
* @return {string} Course title.
|
||||||
*/
|
*/
|
||||||
getCourseTitle(course: any) : string {
|
getCourseTitle(course: any) : string {
|
||||||
return this.executeFunction(course.format, 'getCourseTitle', course.fullname || '', [course]);
|
return this.executeFunction(course.format, 'getCourseTitle', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,20 +238,17 @@ export class CoreCourseFormatDelegate {
|
||||||
*
|
*
|
||||||
* @param {any} course The course to get the title.
|
* @param {any} course The course to get the title.
|
||||||
* @param {any[]} sections List of sections.
|
* @param {any[]} sections List of sections.
|
||||||
* @return {any} Current section.
|
* @return {Promise<any>} Promise resolved with current section.
|
||||||
*/
|
*/
|
||||||
getCurrentSection(course: any, sections: any[]) : any {
|
getCurrentSection(course: any, sections: any[]) : Promise<any> {
|
||||||
// Calculate default section (the first one that isn't all sections).
|
// Convert the result to a Promise if it isn't.
|
||||||
let defaultSection;
|
return Promise.resolve(this.executeFunction(course.format, 'getCurrentSection', [course, sections])).catch(() => {
|
||||||
for (let i = 0; i < sections.length; i++) {
|
// This function should never fail. Just return the first section.
|
||||||
let section = sections[i];
|
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||||
if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
return sections[0];
|
||||||
defaultSection = section;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
return sections[1];
|
||||||
|
});
|
||||||
return this.executeFunction(course.format, 'getCurrentSection', defaultSection, [course, sections]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,7 +258,7 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {any} The component to use, undefined if not found.
|
* @return {any} The component to use, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getSectionSelectorComponent(course: any) : any {
|
getSectionSelectorComponent(course: any) : any {
|
||||||
return this.executeFunction(course.format, 'getSectionSelectorComponent', undefined, [course]);
|
return this.executeFunction(course.format, 'getSectionSelectorComponent', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -260,7 +269,18 @@ export class CoreCourseFormatDelegate {
|
||||||
* @return {any} The component to use, undefined if not found.
|
* @return {any} The component to use, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getSingleSectionComponent(course: any) : any {
|
getSingleSectionComponent(course: any) : any {
|
||||||
return this.executeFunction(course.format, 'getSingleSectionComponent', undefined, [course]);
|
return this.executeFunction(course.format, 'getSingleSectionComponent', [course]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the data required to load the course format.
|
||||||
|
*
|
||||||
|
* @param {any} course The course to get the title.
|
||||||
|
* @param {any[]} sections List of sections.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateData(course: any, sections: any[]) : Promise<any> {
|
||||||
|
return this.executeFunction(course.format, 'invalidateData', [course, sections]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue