diff --git a/src/core/course/pages/section/section.html b/src/core/course/pages/section/section.html
index 2f47b9f38..ea12ab46f 100644
--- a/src/core/course/pages/section/section.html
+++ b/src/core/course/pages/section/section.html
@@ -15,6 +15,7 @@
+
diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts
index 7246d3fd9..2ef55918c 100644
--- a/src/core/course/pages/section/section.ts
+++ b/src/core/course/pages/section/section.ts
@@ -24,7 +24,8 @@ import { CoreCourseProvider } from '../../providers/course';
import { CoreCourseHelperProvider } from '../../providers/helper';
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate';
-import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate';
+import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay,
+ CoreCourseOptionsMenuHandlerToDisplay } from '../../providers/options-delegate';
import { CoreCourseSyncProvider } from '../../providers/sync';
import { CoreCourseFormatComponent } from '../../components/format/format';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
@@ -49,6 +50,7 @@ export class CoreCourseSectionPage implements OnDestroy {
sectionId: number;
sectionNumber: number;
courseHandlers: CoreCourseOptionsHandlerToDisplay[];
+ courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
dataLoaded: boolean;
downloadEnabled: boolean;
downloadEnabledIcon = 'square-outline'; // Disabled by default.
@@ -301,6 +303,11 @@ export class CoreCourseSectionPage implements OnDestroy {
}
}));
+ // Load the course menu handlers.
+ promises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, this.course).then((handlers) => {
+ this.courseMenuHandlers = handlers;
+ }));
+
// Load the course format options when course completion is enabled to show completion progress on sections.
if (this.course.enablecompletion && this.coursesProvider.isGetCoursesByFieldAvailable()) {
promises.push(this.coursesProvider.getCoursesByField('id', this.course.id).catch(() => {
@@ -417,7 +424,8 @@ export class CoreCourseSectionPage implements OnDestroy {
* Prefetch the whole course.
*/
prefetchCourse(): void {
- this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, this.sections, this.courseHandlers)
+ this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, this.sections,
+ this.courseHandlers, this.courseMenuHandlers)
.then(() => {
if (this.downloadEnabled) {
// Recalculate the status.
@@ -459,6 +467,16 @@ export class CoreCourseSectionPage implements OnDestroy {
this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: this.course, avoidOpenCourse: true});
}
+ /**
+ * Opens a menu item registered to the delegate.
+ *
+ * @param {CoreCourseMenuHandlerToDisplay} item Item to open
+ */
+ openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void {
+ const params = Object.assign({ course: this.course}, item.data.pageParams);
+ this.navCtrl.push(item.data.page, params);
+ }
+
/**
* Page destroyed.
*/
diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts
index aa57300b6..e137ac30c 100644
--- a/src/core/course/providers/helper.ts
+++ b/src/core/course/providers/helper.ts
@@ -25,7 +25,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
-import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from './options-delegate';
+import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay,
+ CoreCourseOptionsMenuHandlerToDisplay } from './options-delegate';
import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreCourseProvider } from './course';
@@ -273,10 +274,11 @@ export class CoreCourseHelperProvider {
* @param {any} course Course to prefetch.
* @param {any[]} [sections] List of course sections.
* @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course handlers.
+ * @param {CoreCourseOptionsMenuHandlerToDisplay[]} menuHandlers List of course menu handlers.
* @return {Promise} Promise resolved when the download finishes, rejected if an error occurs or the user cancels.
*/
- confirmAndPrefetchCourse(data: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[])
- : Promise {
+ confirmAndPrefetchCourse(data: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[],
+ menuHandlers?: CoreCourseOptionsMenuHandlerToDisplay[]): Promise {
const initialIcon = data.prefetchCourseIcon,
initialTitle = data.title,
@@ -297,15 +299,23 @@ export class CoreCourseHelperProvider {
// Confirm the download.
return this.confirmDownloadSizeSection(course.id, undefined, sections, true).then(() => {
// User confirmed, get the course handlers if needed.
- if (courseHandlers) {
- promise = Promise.resolve(courseHandlers);
- } else {
- promise = this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course);
+ const subPromises = [];
+ if (!courseHandlers) {
+ subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course)
+ .then((cHandlers) => {
+ courseHandlers = cHandlers;
+ }));
+ }
+ if (!menuHandlers) {
+ subPromises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, course)
+ .then((mHandlers) => {
+ menuHandlers = mHandlers;
+ }));
}
- return promise.then((handlers: CoreCourseOptionsHandlerToDisplay[]) => {
+ return Promise.all(subPromises).then(() => {
// Now we have all the data, download the course.
- return this.prefetchCourse(course, sections, handlers, siteId);
+ return this.prefetchCourse(course, sections, courseHandlers, menuHandlers, siteId);
}).then(() => {
// Download successful.
return true;
@@ -340,6 +350,7 @@ export class CoreCourseHelperProvider {
const subPromises = [];
let sections,
handlers,
+ menuHandlers,
success = true;
// Get the sections and the handlers.
@@ -349,9 +360,12 @@ export class CoreCourseHelperProvider {
subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course).then((cHandlers) => {
handlers = cHandlers;
}));
+ subPromises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, course).then((mHandlers) => {
+ menuHandlers = mHandlers;
+ }));
promises.push(Promise.all(subPromises).then(() => {
- return this.prefetchCourse(course, sections, handlers, siteId);
+ return this.prefetchCourse(course, sections, handlers, menuHandlers, siteId);
}).catch((error) => {
success = false;
@@ -1162,11 +1176,12 @@ export class CoreCourseHelperProvider {
* @param {any} course The course to prefetch.
* @param {any[]} sections List of course sections.
* @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course options handlers.
+ * @param {CoreCourseOptionsMenuHandlerToDisplay[]} courseMenuHandlers List of course menu handlers.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise} Promise resolved when the download finishes.
*/
- prefetchCourse(course: any, sections: any[], courseHandlers: CoreCourseOptionsHandlerToDisplay[], siteId?: string)
- : Promise {
+ prefetchCourse(course: any, sections: any[], courseHandlers: CoreCourseOptionsHandlerToDisplay[],
+ courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[], siteId?: string): Promise {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][course.id]) {
@@ -1195,6 +1210,11 @@ export class CoreCourseHelperProvider {
promises.push(handler.prefetch(course));
}
});
+ courseMenuHandlers.forEach((handler) => {
+ if (handler.prefetch) {
+ promises.push(handler.prefetch(course));
+ }
+ });
// Prefetch other data needed to render the course.
if (this.coursesProvider.isGetCoursesByFieldAvailable()) {
diff --git a/src/core/course/providers/options-delegate.ts b/src/core/course/providers/options-delegate.ts
index 961c61939..cccfde281 100644
--- a/src/core/course/providers/options-delegate.ts
+++ b/src/core/course/providers/options-delegate.ts
@@ -31,6 +31,12 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
*/
priority: number;
+ /**
+ * True if this handler should appear in menu rather than as a tab.
+ * @type {boolean}
+ */
+ isMenuHandler?: boolean;
+
/**
* Whether or not the handler is enabled for a certain course.
*
@@ -70,6 +76,21 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
prefetch?(course: any): Promise;
}
+/**
+ * Interface that course options handlers implement if they appear in the menu rather than as a tab.
+ */
+export interface CoreCourseOptionsMenuHandler extends CoreCourseOptionsHandler {
+ /**
+ * Returns the data needed to render the handler.
+ *
+ * @param {Injector} injector Injector.
+ * @param {number} courseId The course ID.
+ * @return {CoreCourseOptionsMenuHandlerData|Promise} Data or promise resolved with data.
+ */
+ getMenuDisplayData(injector: Injector, courseId: number):
+ CoreCourseOptionsMenuHandlerData | Promise;
+}
+
/**
* Data needed to render a course handler. It's returned by the handler.
*/
@@ -99,6 +120,41 @@ export interface CoreCourseOptionsHandlerData {
componentData?: any;
}
+/**
+ * Data needed to render a course menu handler. It's returned by the handler.
+ */
+export interface CoreCourseOptionsMenuHandlerData {
+ /**
+ * Title to display for the handler.
+ * @type {string}
+ */
+ title: string;
+
+ /**
+ * Class to add to the displayed handler.
+ * @type {string}
+ */
+ class?: string;
+
+ /**
+ * Name of the page to load for the handler.
+ * @type {string}
+ */
+ page: string;
+
+ /**
+ * Params to pass to the page (other than 'course' which is always sent).
+ * @type {any}
+ */
+ pageParams?: any;
+
+ /**
+ * Name of the icon to display for the handler.
+ * @type {string}
+ */
+ icon: string; // Name of the icon to display in the tab.
+}
+
/**
* Data returned by the delegate for each handler.
*/
@@ -130,6 +186,37 @@ export interface CoreCourseOptionsHandlerToDisplay {
prefetch?(course: any): Promise;
}
+/**
+ * Additional data returned if it is a menu item.
+ */
+export interface CoreCourseOptionsMenuHandlerToDisplay {
+ /**
+ * Data to display.
+ * @type {CoreCourseOptionsMenuHandlerData}
+ */
+ data: CoreCourseOptionsMenuHandlerData;
+
+ /**
+ * Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
+ * @type {string}
+ */
+ name: string;
+
+ /**
+ * The highest priority is displayed first.
+ * @type {number}
+ */
+ priority?: number;
+
+ /**
+ * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline.
+ *
+ * @param {any} course The course.
+ * @return {Promise} Promise resolved when done.
+ */
+ prefetch?(course: any): Promise;
+}
+
/**
* Service to interact with plugins to be shown in each course (participants, learning plans, ...).
*/
@@ -139,7 +226,8 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
protected lastUpdateHandlersForCoursesStart: any = {};
protected coursesHandlers: {
[courseId: number]: {
- access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer, enabledHandlers?: CoreCourseOptionsHandler[]
+ access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer,
+ enabledHandlers?: CoreCourseOptionsHandler[], enabledMenuHandlers?: CoreCourseOptionsMenuHandler[]
}
} = {};
@@ -258,6 +346,43 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
*/
getHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any):
Promise {
+ return > this.getHandlersToDisplayInternal(
+ false, injector, course, refresh, isGuest, navOptions, admOptions);
+ }
+
+ /**
+ * Get the list of menu handlers that should be displayed for a course.
+ * This function should be called only when the handlers need to be displayed, since it can call several WebServices.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} course The course object.
+ * @param {boolean} [refresh] True if it should refresh the list.
+ * @param {boolean} [isGuest] Whether it's guest.
+ * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
+ * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
+ * @return {Promise} Promise resolved with array of handlers.
+ */
+ getMenuHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean,
+ navOptions?: any, admOptions?: any): Promise {
+ return > this.getHandlersToDisplayInternal(
+ true, injector, course, refresh, isGuest, navOptions, admOptions);
+ }
+
+ /**
+ * Get the list of menu handlers that should be displayed for a course.
+ * This function should be called only when the handlers need to be displayed, since it can call several WebServices.
+ *
+ * @param {boolean} menu If true, gets menu handlers; false, gets tab handlers
+ * @param {Injector} injector Injector.
+ * @param {any} course The course object.
+ * @param {boolean} refresh True if it should refresh the list.
+ * @param {boolean} isGuest Whether it's guest.
+ * @param {any} navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
+ * @param {any} admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
+ * @return {Promise} Promise resolved with array of handlers.
+ */
+ protected getHandlersToDisplayInternal(menu: boolean, injector: Injector, course: any, refresh: boolean, isGuest: boolean,
+ navOptions: any, admOptions: any): Promise {
course.id = parseInt(course.id, 10);
const accessData = {
@@ -278,8 +403,16 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
}).then(() => {
const promises = [];
- this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => {
- promises.push(Promise.resolve(handler.getDisplayData(injector, course)).then((data) => {
+ let handlerList;
+ if (menu) {
+ handlerList = this.coursesHandlers[course.id].enabledMenuHandlers;
+ } else {
+ handlerList = this.coursesHandlers[course.id].enabledHandlers;
+ }
+
+ handlerList.forEach((handler) => {
+ const getFunction = menu ? handler.getMenuDisplayData : handler.getDisplayData;
+ promises.push(Promise.resolve(getFunction.call(handler, injector, course)).then((data) => {
handlersToDisplay.push({
data: data,
priority: handler.priority,
@@ -444,6 +577,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): Promise {
const promises = [],
enabledForCourse = [],
+ enabledForCourseMenu = [],
siteId = this.sitesProvider.getCurrentSiteId(),
now = Date.now();
@@ -456,7 +590,11 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
promises.push(Promise.resolve(handler.isEnabledForCourse(courseId, accessData, navOptions, admOptions))
.then((enabled) => {
if (enabled) {
- enabledForCourse.push(handler);
+ if (handler.isMenuHandler) {
+ enabledForCourseMenu.push( handler);
+ } else {
+ enabledForCourse.push(handler);
+ }
} else {
return Promise.reject(null);
}
@@ -476,6 +614,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
if (this.isLastUpdateCourseCall(courseId, now) && this.sitesProvider.getCurrentSiteId() === siteId) {
// Update the coursesHandlers array with the new enabled addons.
this.coursesHandlers[courseId].enabledHandlers = enabledForCourse;
+ this.coursesHandlers[courseId].enabledMenuHandlers = enabledForCourseMenu;
this.loaded[courseId] = true;
// Resolve the promise.