MOBILE-2323 learning plans: Migrate learning plans

main
Albert Gasset 2018-03-09 15:21:18 +01:00
parent bb524a384d
commit 61ab280b2d
29 changed files with 2396 additions and 0 deletions

View File

@ -0,0 +1,68 @@
// (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 { AddonCompetencyProvider } from './providers/competency';
import { AddonCompetencyHelperProvider } from './providers/helper';
import { AddonCompetencyCourseOptionHandler } from './providers/course-option-handler';
import { AddonCompetencyMainMenuHandler } from './providers/mainmenu-handler';
import { AddonCompetencyUserHandler } from './providers/user-handler';
import { AddonCompetencyComponentsModule } from './components/components.module';
import { CoreCourseProvider } from '../../core/course/providers/course';
import { CoreCourseOptionsDelegate } from '../../core/course/providers/options-delegate';
import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
import { CoreUserDelegate } from '../../core/user/providers/user-delegate';
import { CoreUserProvider } from '../../core/user/providers/user';
import { CoreEventsProvider } from '../../providers/events';
import { CoreSitesProvider } from '../../providers/sites';
import { CoreCoursesProvider } from '../../core/courses/providers/courses';
@NgModule({
declarations: [
],
imports: [
AddonCompetencyComponentsModule
],
providers: [
AddonCompetencyProvider,
AddonCompetencyHelperProvider,
AddonCompetencyCourseOptionHandler,
AddonCompetencyMainMenuHandler,
AddonCompetencyUserHandler
]
})
export class AddonCompetencyModule {
constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: AddonCompetencyMainMenuHandler,
courseOptionsDelegate: CoreCourseOptionsDelegate, courseOptionHandler: AddonCompetencyCourseOptionHandler,
userDelegate: CoreUserDelegate, userHandler: AddonCompetencyUserHandler,
eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) {
mainMenuDelegate.registerHandler(mainMenuHandler);
courseOptionsDelegate.registerHandler(courseOptionHandler);
userDelegate.registerHandler(userHandler);
eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
courseOptionHandler.clearCoursesNavCache();
userHandler.clearUsersNavCache();
}, sitesProvider.getCurrentSiteId());
eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED, () => {
courseOptionHandler.clearCoursesNavCache();
}, sitesProvider.getCurrentSiteId());
eventsProvider.on(CoreUserProvider.PROFILE_REFRESHED, () => {
userHandler.clearUsersNavCache();
}, sitesProvider.getCurrentSiteId());
}
}

View File

@ -0,0 +1,45 @@
// (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 { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '../../../components/components.module';
import { CoreDirectivesModule } from '../../../directives/directives.module';
import { CorePipesModule } from '../../../pipes/pipes.module';
import { AddonCompetencyCourseComponent } from './course/course';
@NgModule({
declarations: [
AddonCompetencyCourseComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule
],
providers: [
],
exports: [
AddonCompetencyCourseComponent
],
entryComponents: [
AddonCompetencyCourseComponent
]
})
export class AddonCompetencyComponentsModule {}

View File

@ -0,0 +1,73 @@
<ion-content>
<ion-refresher [enabled]="competenciesLoaded" (ionRefresh)="refreshCourseCompetencies($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="competenciesLoaded">
<ion-card *ngIf="!user && competencies && competencies.statistics.competencycount > 0">
<ion-item text-wrap *ngIf="competencies.settings.pushratingstouserplans">
{{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }}
</ion-item>
<ion-item text-wrap *ngIf="!competencies.settings.pushratingstouserplans">
{{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }}
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.progress' | translate }}</strong>:
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate:{$a: {x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }} ({{ competencies.statistics.proficientcompetencypercentageformatted }}%)
<core-progress-bar [progress]="competencies.statistics.proficientcompetencypercentage"></core-progress-bar>
</ion-item>
<ion-item text-wrap *ngIf="competencies.statistics.leastproficientcount > 0">
<strong>{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</strong>:
<p *ngFor="let comp of competencies.statistics.leastproficient">
<a ui-sref="openCompetencySummary(comp.id)">
{{ comp.shortname }} - {{ comp.idnumber }}
</a>
</p>
</ion-item>
</ion-card>
<h3 margin-horizontal *ngIf="competencies && competencies.statistics.competencycount > 0">{{ 'addon.competency.competencies' | translate }}</h3>
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar *ngIf="user.profileimageurl && user.profileimageurl !== true" item-start>
<img [src]="user.profileimageurl" [alt]="'core.pictureof' | translate:{$a: user.fullname}" core-external-content>
</ion-avatar>
<span *ngIf="user.profileimageurl === true" item-start>
<ion-icon name="person"></ion-icon>
</span>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
</ion-item>
</ion-card>
<core-empty-box *ngIf="competencies && competencies.statistics.competencycount == 0" icon="ribbon" message="{{ 'addon.competency.nocompetencies' | translate }}"></core-empty-box>
<div *ngIf="competencies">
<ion-card *ngFor="let competency of competencies.competencies">
<a ion-item text-wrap (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname">
{{competency.competency.shortname}} <small>{{competency.competency.idnumber}}</small>
<ion-badge item-end *ngIf="competency.usercompetencycourse && competency.usercompetencycourse.gradename" [color]="competency.usercompetencycourse.proficiency ? 'success' : 'danger'">{{ competency.usercompetencycourse.gradename }}</ion-badge><br>
</a>
<ion-item text-wrap>
<div *ngIf="competency.competency.description">
<core-format-text [text]=" competency.competency.description "></core-format-text>
</div>
<div>
<strong>{{ 'addon.competency.path' | translate }}</strong>:
{{ competency.comppath.framework.name }}
<span *ngFor="let ancestor of competency.comppath.ancestors">
&nbsp;/&nbsp;<a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
</span>
</div>
<div>
<strong>{{ 'addon.competency.activities' | translate }}</strong>:
<span *ngIf="competency.coursemodules.length == 0">
{{ 'addon.competency.noactivities' | translate }}
</span>
<a ion-item text-wrap *ngFor="let activity of competency.coursemodules" [href]="activity.url" [title]="activity.name">
<img item-start [src]="activity.iconurl" core-external-content alt="" role="presentation" *ngIf="activity.iconurl" class="core-module-icon">
<core-format-text [text]="activity.name"></core-format-text>
</a>
</div>
</ion-item>
</ion-card>
</div>
</core-loading>
</ion-content>

View File

@ -0,0 +1,112 @@
// (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, Input, Optional } from '@angular/core';
import { Content, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '../../../../providers/app';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
/**
* Component that displays the competencies of a course.
*/
@Component({
selector: 'addon-competency-course',
templateUrl: 'course.html',
})
export class AddonCompetencyCourseComponent {
@ViewChild(Content) content: Content;
@Input() courseId: number;
@Input() userId: number;
competenciesLoaded = false;
competencies: any;
user: any;
constructor(private navCtrl: NavController, private translate: TranslateService,
private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider,
private competencyProvider: AddonCompetencyProvider, private helperProvider: AddonCompetencyHelperProvider) {
}
/**
* View loaded.
*/
ngOnInit(): void {
this.fetchCourseCompetencies().finally(() => {
this.competenciesLoaded = true;
});
}
/**
* Fetches the competencies and updates the view.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchCourseCompetencies(): Promise<void> {
return this.competencyProvider.getCourseCompetencies(this.courseId, this.userId).then((competencies) => {
this.competencies = competencies;
// Get the user profile image.
this.helperProvider.getProfile(this.userId).then((user) => {
this.user = user;
});
}, (message) => {
if (message) {
this.domUtils.showErrorModal(message);
} else {
this.domUtils.showErrorModal('Error getting course competencies data.');
}
return Promise.reject(null);
});
}
/**
* Opens a competency.
*
* @param {number} competencyId
*/
openCompetency(competencyId: number): void {
if (this.appProvider.isWide()) {
this.navCtrl.push('AddonCompetencyCompetenciesPage', {competencyId, courseId: this.courseId, userId: this.userId});
} else {
this.navCtrl.push('AddonCompetencyCompetencyPage', {competencyId, courseId: this.courseId, userId: this.userId});
}
}
/**
* Opens the summary of a competency.
*
* @param {number} competencyId
*/
openCompetencySummary(competencyId: number): void {
this.navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId});
}
/**
* Refreshes the competencies.
*
* @param {any} refresher Refresher.
*/
refreshCourseCompetencies(refresher: any): void {
this.competencyProvider.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => {
this.fetchCourseCompetencies().finally(() => {
refresher.complete();
});
});
}
}

View File

@ -0,0 +1,47 @@
{
"activities": "Activities",
"competencies": "Competencies",
"competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course",
"coursecompetencies": "Course competencies",
"coursecompetencyratingsarenotpushedtouserplans": "Competency ratings in this course do not affect learning plans.",
"coursecompetencyratingsarepushedtouserplans": "Competency ratings in this course are updated immediately in learning plans.",
"crossreferencedcompetencies": "Cross-referenced competencies",
"duedate": "Due date",
"errornocompetenciesfound": "No competencies found",
"evidence": "Evidence",
"evidence_competencyrule": "The rule of the competency was met.",
"evidence_coursecompleted": "The course '{{$a}}' was completed.",
"evidence_coursemodulecompleted": "The activity '{{$a}}' was completed.",
"evidence_courserestored": "The rating was restored along with the course '{{$a}}'.",
"evidence_evidenceofpriorlearninglinked": "The evidence of prior learning '{{$a}}' was linked.",
"evidence_evidenceofpriorlearningunlinked": "The evidence of prior learning '{{$a}}' was unlinked.",
"evidence_manualoverride": "The competency rating was manually set.",
"evidence_manualoverrideincourse": "The competency rating was manually set in the course '{{$a}}'.",
"evidence_manualoverrideinplan": "The competency rating was manually set in the learning plan '{{$a}}'.",
"learningplancompetencies": "Learning plan competencies",
"learningplans": "Learning plans",
"myplans": "My learning plans",
"noactivities": "No activities",
"nocompetencies": "No competencies",
"nocrossreferencedcompetencies": "No other competencies have been cross-referenced to this competency.",
"noevidence": "No evidence",
"noplanswerecreated": "No learning plans were created.",
"path": "Path:",
"planstatusactive": "Active",
"planstatuscomplete": "Complete",
"planstatusdraft": "Draft",
"planstatusinreview": "In review",
"planstatuswaitingforreview": "Waiting for review",
"proficient": "Proficient",
"progress": "Progress",
"rating": "Rating",
"reviewstatus": "Review status",
"status": "Status",
"template": "Learning plan template",
"usercompetencystatus_idle": "Idle",
"usercompetencystatus_inreview": "In review",
"usercompetencystatus_waitingforreview": "Waiting for review",
"userplans": "Learning plans",
"xcompetenciesproficientoutofy": "{{$a.x}} out of {{$a.y}} competencies are proficient",
"xcompetenciesproficientoutofyincourse": "You are proficient in {{$a.x}} out of {{$a.y}} competencies in this course."
}

View File

@ -0,0 +1,21 @@
<ion-header>
<ion-navbar>
<ion-title>{{ title }}</ion-title>
</ion-navbar>
</ion-header>
<core-split-view>
<ion-content>
<ion-refresher [enabled]="competenciesLoaded" (ionRefresh)="refreshCompetencies($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="competenciesLoaded">
<ion-list>
<a ion-item text-wrap *ngFor="let competency of competencies" [title]="competency.competency.shortname" (click)="openCompetency(competency.competency.id)" [class.core-split-item-selected]="competency.competency.id == competencyId">
{{ competency.competency.shortname }} <small>{{competency.competency.idnumber}}</small>
<ion-badge item-end *ngIf="competency.usercompetency" [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge>
<ion-badge item-end *ngIf="competency.usercompetencycourse" [color]="competency.usercompetencycourse.proficiency ? 'success' : 'danger'">{{ competency.usercompetencycourse.gradename }}</ion-badge>
</a>
</ion-list>
</core-loading>
</ion-content>
</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 { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { AddonCompetencyCompetenciesPage } from './competencies';
@NgModule({
declarations: [
AddonCompetencyCompetenciesPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonCompetencyCompetenciesPage),
TranslateModule.forChild()
],
})
export class AddonCompetencyCompetenciesPageModule {}

View File

@ -0,0 +1,143 @@
// (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, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
/**
* Page that displays the list of competencies of a learning plan.
*/
@IonicPage({ segment: 'addon-competency-competencies' })
@Component({
selector: 'page-addon-competency-competencies',
templateUrl: 'competencies.html',
})
export class AddonCompetencyCompetenciesPage {
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
protected planId: number;
protected courseId: number;
protected competencyId: number;
protected userId: number;
competenciesLoaded = false;
competencies = [];
title: string;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider) {
this.planId = navParams.get('planId');
this.courseId = navParams.get('courseId');
this.competencyId = navParams.get('competencyId');
this.userId = navParams.get('userId');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
if (this.competencyId) {
// There is a competency to load.
this.openCompetency(this.competencyId);
}
this.fetchCompetencies().then(() => {
if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) {
// Take first and load it.
this.openCompetency(this.competencies[0].id);
}
}).finally(() => {
this.competenciesLoaded = true;
});
}
/**
* Fetches the competencies and updates the view.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchCompetencies(): Promise<void> {
let promise;
if (this.planId) {
promise = this.competencyProvider.getLearningPlan(this.planId);
} else if (this.courseId) {
promise = this.competencyProvider.getCourseCompetencies(this.courseId, this.userId);
} else {
promise = Promise.reject(null);
}
return promise.then((response) => {
if (response.competencycount <= 0) {
return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound'));
}
if (this.planId) {
this.title = response.plan.name;
this.userId = response.plan.userid;
} else {
this.title = this.translate.instant('addon.competency.coursecompetencies');
}
this.competencies = response.competencies;
}).catch((message) => {
if (message) {
this.domUtils.showErrorModal(message);
} else {
this.domUtils.showErrorModal('Error getting competencies data.');
}
return Promise.reject(null);
});
}
/**
* Opens a competency.
*
* @param {number} competencyId
*/
openCompetency(competencyId: number): void {
this.competencyId = competencyId;
let params;
if (this.planId) {
params = {competencyId, planId: this.planId};
} else {
params = {competencyId, courseId: this.courseId, userId: this.userId};
}
this.splitviewCtrl.push('AddonCompetencyCompetencyPage', params);
}
/**
* Refreshes the competencies.
*
* @param {any} refresher Refresher.
*/
refreshCompetencies(refresher: any): void {
let promise;
if (this.planId) {
promise = this.competencyProvider.invalidateLearningPlan(this.planId);
} else {
promise = this.competencyProvider.invalidateCourseCompetencies(this.courseId, this.userId);
}
return promise.finally(() => {
this.fetchCompetencies().finally(() => {
refresher.complete();
});
});
}
}

View File

@ -0,0 +1,95 @@
<ion-header>
<ion-navbar>
<ion-title *ngIf="competency">{{ competency.competency.competency.shortname }} <small>{{ competency.competency.competency.idnumber }}</small></ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="competencyLoaded" (ionRefresh)="refreshCompetency($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="competencyLoaded">
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar *ngIf="user.profileimageurl && user.profileimageurl !== true" item-start>
<img [src]="user.profileimageurl" [alt]="'core.pictureof' | translate:{$a: user.fullname}" core-external-content>
</ion-avatar>
<span *ngIf="user.profileimageurl === true" item-start>
<ion-icon name="person"></ion-icon>
</span>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
</ion-item>
</ion-card>
<ion-card *ngIf="competency">
<ion-item text-wrap *ngIf="competency.competency.competency.description">
<core-format-text [text]="competency.competency.competency.description"></core-format-text>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.path' | translate }}</strong>:
{{ competency.competency.comppath.framework.name }}
<span *ngFor="let ancestor of competency.competency.comppath.ancestors">
&nbsp;/&nbsp;<a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
</span>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.crossreferencedcompetencies' | translate }}</strong>:
<div *ngIf="!competency.competency.hasrelatedcompetencies">{{ 'addon.competency.nocrossreferencedcompetencies' | translate }}</div>
<div *ngIf="competency.competency.hasrelatedcompetencies">
<p *ngFor="let relatedcomp of competency.competency.relatedcompetencies">
<a (click)="openCompetencySummary(relatedcomp.id)">
{{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }}
</a>
</p>
</div>
</ion-item>
<ion-item text-wrap *ngIf="coursemodules">
<strong>{{ 'addon.competency.activities' | translate }}</strong>:
<span *ngIf="coursemodules.length == 0">
{{ 'addon.competency.noactivities' | translate }}
</span>
<a ion-item text-wrap *ngFor="let activity of coursemodules" [href]="activity.url" [title]="activity.name">
<img item-start core-external-content [src]="activity.iconurl" alt="" role="presentation" *ngIf="activity.iconurl" class="core-module-icon">
<core-format-text [text]="activity.name"></core-format-text>
</a>
</ion-item>
<ion-item text-wrap *ngIf="competency.usercompetency.status">
<strong>{{ 'addon.competency.reviewstatus' | translate }}</strong>:
{{ competency.usercompetency.statusname }}
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.proficient' | translate }}</strong>:
<ion-badge color="success" *ngIf="competency.usercompetency.proficiency">
{{ 'core.yes' | translate }}
</ion-badge>
<ion-badge color="danger" *ngIf="!competency.usercompetency.proficiency">
{{ 'core.no' | translate }}
</ion-badge>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.rating' | translate }}</strong>:
<ion-badge color="dark">{{ competency.usercompetency.gradename }}</ion-badge>
</ion-item>
</ion-card>
<div *ngIf="competency">
<h3 margin-horizontal>{{ 'addon.competency.evidence' | translate }}</h3>
<p margin-horizontal *ngIf="competency.evidence.length == 0">
{{ 'addon.competency.noevidence' | translate }}
</p>
<ion-card *ngFor="let evidence of competency.evidence">
<a ion-item text-wrap *ngIf="evidence.actionuser" (click)="openUserProfile(evidence.actionuser.id)">
<ion-avatar item-start>
<img core-external-content [src]="evidence.actionuser.profileimageurlsmall" [alt]="'core.pictureof' | translate:{$a: evidence.actionuser.fullname}" role="presentation">
</ion-avatar>
<h2>{{ evidence.actionuser.fullname }}</h2>
<p>{{ evidence.timemodified | coreToLocaleString }}</p>
</a>
<ion-item text-wrap>
<p><ion-badge color="dark">{{ evidence.gradename }}</ion-badge></p>
<p margin-top *ngIf="evidence.description">{{ evidence.description }}</p>
<blockquote *ngIf="evidence.note"><core-format-text [text]="evidence.note"></core-format-text></blockquote>
</ion-item>
</ion-card>
</div>
</core-loading>
</ion-content>

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 { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { AddonCompetencyCompetencyPage } from './competency';
@NgModule({
declarations: [
AddonCompetencyCompetencyPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonCompetencyCompetencyPage),
TranslateModule.forChild()
],
})
export class AddonCompetencyCompetencyPageModule {}

View File

@ -0,0 +1,184 @@
// (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, Optional } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '../../../../providers/sites';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
/**
* Page that displays a learning plan.
*/
@IonicPage({ segment: 'addon-competency-competency' })
@Component({
selector: 'page-addon-competency-competency',
templateUrl: 'competency.html',
})
export class AddonCompetencyCompetencyPage {
competencyLoaded = false;
competencyId: number;
planId: number;
courseId: number;
userId: number;
planStatus: number;
coursemodules: any;
user: any;
competency: any;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
@Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) {
this.competencyId = navParams.get('competencyId');
this.planId = navParams.get('planId');
this.courseId = navParams.get('courseId');
this.userId = navParams.get('userId');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchCompetency().then(() => {
if (this.planId) {
this.competencyProvider.logCompetencyInPlanView(this.planId, this.competencyId, this.planStatus, this.userId);
} else {
this.competencyProvider.logCompetencyInCourseView(this.courseId, this.competencyId, this.userId);
}
}).finally(() => {
this.competencyLoaded = true;
});
}
/**
* Fetches the competency and updates the view.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchCompetency(): Promise<void> {
let promise;
if (this.planId) {
this.planStatus = null;
promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId);
} else if (this.courseId) {
promise = this.competencyProvider.getCompetencyInCourse(this.courseId, this.competencyId, this.userId);
} else {
promise = Promise.reject(null);
}
return promise.then((competency) => {
this.competency = competency.usercompetencysummary;
if (this.planId) {
this.planStatus = competency.plan.status;
this.competency.usercompetency.statusname = this.getStatusName(this.competency.usercompetency.status);
} else {
this.competency.usercompetency = this.competency.usercompetencycourse;
this.coursemodules = competency.coursemodules;
}
if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) {
this.competency.user.profileimageurl = this.competency.user.profileimageurl || true;
// Get the user profile image from the returned object.
this.user = this.competency.user;
}
this.competency.evidence.forEach((evidence) => {
if (evidence.descidentifier) {
const key = 'addon.competency.' + evidence.descidentifier;
evidence.description = this.translate.instant(key, {$a: evidence.desca});
}
});
}, (message) => {
if (message) {
this.domUtils.showErrorModal(message);
} else {
this.domUtils.showErrorModal('Error getting competency data.');
}
return Promise.reject(null);
});
}
/**
* Convenience function to get the review status name translated.
*
* @param {number} status
* @return {any}
*/
protected getStatusName(status: number): any {
let statusTranslateName;
switch (status) {
case AddonCompetencyProvider.REVIEW_STATUS_IDLE:
statusTranslateName = 'idle';
break;
case AddonCompetencyProvider.REVIEW_STATUS_IN_REVIEW:
statusTranslateName = 'inreview';
break;
case AddonCompetencyProvider.REVIEW_STATUS_WAITING_FOR_REVIEW:
statusTranslateName = 'waitingforreview';
break;
default:
// We can use the current status name.
return status;
}
return this.translate.instant('addon.competency.usercompetencystatus_' + statusTranslateName);
}
/**
* Refreshes the competency.
*
* @param {any} refresher Refresher.
*/
refreshCompetency(refresher: any): void {
let promise;
if (this.planId) {
promise = this.competencyProvider.invalidateCompetencyInPlan(this.planId, this.competencyId);
} else {
promise = this.competencyProvider.invalidateCompetencyInCourse(this.courseId, this.competencyId);
}
return promise.finally(() => {
this.fetchCompetency().finally(() => {
refresher.complete();
});
});
}
/**
* Opens the summary of a competency.
*
* @param {number} competencyId
*/
openCompetencySummary(competencyId: number): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId});
}
/**
* Opens the profile of a user.
*
* @param {number} userId
*/
openUserProfile(userId: number): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('CoreUserProfilePage', {userId, courseId: this.courseId});
}
}

View File

@ -0,0 +1,24 @@
<ion-header>
<ion-navbar>
<ion-title *ngIf="competency">{{ competency.competency.shortname }} <small>{{ competency.competency.idnumber }}</small></ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="competencyLoaded" (ionRefresh)="refreshCompetency($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="competencyLoaded">
<ion-card *ngIf="competency">
<ion-item text-wrap *ngIf="competency.competency.description">
<core-format-text [text]="competency.competency.description"></core-format-text>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.path' | translate }}</strong>:
{{ competency.comppath.framework.name }}
<span *ngFor="let ancestor of competency.comppath.ancestors">
&nbsp;/&nbsp;<a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
</span>
</ion-item>
</ion-card>
</core-loading>
</ion-content>

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 { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { AddonCompetencyCompetencySummaryPage } from './competencysummary';
@NgModule({
declarations: [
AddonCompetencyCompetencySummaryPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonCompetencyCompetencySummaryPage),
TranslateModule.forChild()
],
})
export class AddonCompetencyCompetencySummaryPageModule {}

View File

@ -0,0 +1,94 @@
// (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, Optional } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
/**
* Page that displays a learning plan.
*/
@IonicPage({ segment: 'addon-competency-competency-summary' })
@Component({
selector: 'page-addon-competency-competency-summary',
templateUrl: 'competencysummary.html',
})
export class AddonCompetencyCompetencySummaryPage {
competencyLoaded = false;
competencyId: number;
competency: any;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private domUtils: CoreDomUtilsProvider, @Optional() private svComponent: CoreSplitViewComponent,
private competencyProvider: AddonCompetencyProvider) {
this.competencyId = navParams.get('competencyId');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchCompetency().then(() => {
this.competencyProvider.logCompetencyView(this.competencyId);
}).finally(() => {
this.competencyLoaded = true;
});
}
/**
* Fetches the competency summary and updates the view.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchCompetency(): Promise<void> {
return this.competencyProvider.getCompetencySummary(this.competencyId).then((competency) => {
this.competency = competency;
}, (message) => {
if (message) {
this.domUtils.showErrorModal(message);
} else {
this.domUtils.showErrorModal('Error getting competency summary data.');
}
return Promise.reject(null);
});
}
/**
* Refreshes the competency summary.
*
* @param {any} refresher Refresher.
*/
refreshCompetency(refresher: any): void {
this.competencyProvider.invalidateCompetencySummary(this.competencyId).finally(() => {
this.fetchCompetency().finally(() => {
refresher.complete();
});
});
}
/**
* Opens the summary of a competency.
*
* @param {number} competencyId
*/
openCompetencySummary(competencyId: number): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId});
}
}

View File

@ -0,0 +1,6 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'addon.competency.coursecompetencies' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<addon-competency-course class="core-avoid-header" [courseId]="courseId" [userId]="userId"></addon-competency-course>

View File

@ -0,0 +1,37 @@
// (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 { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { AddonCompetencyComponentsModule } from '../../components/components.module';
import { AddonCompetencyCourseCompetenciesPage } from './coursecompetencies';
@NgModule({
declarations: [
AddonCompetencyCourseCompetenciesPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonCompetencyCourseCompetenciesPage),
TranslateModule.forChild(),
AddonCompetencyComponentsModule
],
})
export class AddonCompetencyCourseCompetenciesPageModule {}

View File

@ -0,0 +1,42 @@
// (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, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '../../../../providers/app';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
/**
* Page that displays the list of competencies of a course.
*/
@IonicPage({ segment: 'addon-competency-coursecompetencies' })
@Component({
selector: 'page-addon-competency-coursecompetencies',
templateUrl: 'coursecompetencies.html',
})
export class AddonCompetencyCourseCompetenciesPage {
protected courseId: number;
protected userId: number;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider,
private competencyProvider: AddonCompetencyProvider, private helperProvider: AddonCompetencyHelperProvider) {
this.courseId = navParams.get('courseId');
this.userId = navParams.get('userId');
}
}

View File

@ -0,0 +1,56 @@
<ion-header>
<ion-navbar>
<ion-title *ngIf="plan">{{plan.plan.name}}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="planLoaded" (ionRefresh)="refreshLearningPlan($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="planLoaded">
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar *ngIf="user.profileimageurl && user.profileimageurl !== true" item-start>
<img [src]="user.profileimageurl" [alt]="'core.pictureof' | translate:{$a: user.fullname}" core-external-content>
</ion-avatar>
<span *ngIf="user.profileimageurl === true" item-start>
<ion-icon name="person"></ion-icon>
</span>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
</ion-item>
</ion-card>
<ion-card *ngIf="plan">
<ion-list>
<ion-item text-wrap>
<strong>{{ 'addon.competency.status' | translate }}</strong>:
{{ plan.plan.statusname }}
</ion-item>
<ion-item text-wrap *ngIf="plan.plan.duedate > 0">
<strong>{{ 'addon.competency.duedate' | translate }}</strong>:
{{ plan.plan.duedate | coreToLocaleString }}
</ion-item>
<ion-item text-wrap *ngIf="plan.plan.template">
<strong>{{ 'addon.competency.template' | translate }}</strong>:
{{ plan.plan.template.shortname }}
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.progress' | translate }}</strong>:
{{ 'addon.competency.xcompetenciesproficientoutofy' | translate: {$a: {x: plan.proficientcompetencycount, y: plan.competencycount} } }}
<core-progress-bar [progress]="plan.proficientcompetencypercentage" [text]="plan.proficientcompetencypercentageformatted"></core-progress-bar>
</ion-item>
</ion-list>
</ion-card>
<ion-card *ngIf="plan">
<ion-card-header text-wrap>{{ 'addon.competency.learningplancompetencies' | translate }}</ion-card-header>
<ion-list>
<ion-item text-wrap *ngIf="plan.competencycount == 0">
{{ 'addon.competency.nocompetencies' | translate }}
</ion-item>
<a ion-item text-wrap *ngFor="let competency of plan.competencies" (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname">
{{competency.competency.shortname}} <small>{{competency.competency.idnumber}}</small>
<ion-badge item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge>
</a>
</ion-list>
</ion-card>
</core-loading>
</ion-content>

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 { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { AddonCompetencyPlanPage } from './plan';
@NgModule({
declarations: [
AddonCompetencyPlanPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonCompetencyPlanPage),
TranslateModule.forChild()
],
})
export class AddonCompetencyPlanPageModule {}

View File

@ -0,0 +1,132 @@
// (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, Optional } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '../../../../providers/app';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
/**
* Page that displays a learning plan.
*/
@IonicPage({ segment: 'addon-competency-plan' })
@Component({
selector: 'page-addon-competency-plan',
templateUrl: 'plan.html',
})
export class AddonCompetencyPlanPage {
protected planId: number;
planLoaded = false;
plan: any;
user: any;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider,
@Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider,
private competencyHelperProvider: AddonCompetencyHelperProvider) {
this.planId = navParams.get('planId');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchLearningPlan().finally(() => {
this.planLoaded = true;
});
}
/**
* Fetches the learning plan and updates the view.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchLearningPlan(): Promise<void> {
return this.competencyProvider.getLearningPlan(this.planId).then((plan) => {
plan.plan.statusname = this.getStatusName(plan.plan.status);
// Get the user profile image.
this.competencyHelperProvider.getProfile(plan.plan.userid).then((user) => {
this.user = user;
});
this.plan = plan;
}, (message) => {
if (message) {
this.domUtils.showErrorModal(message);
} else {
this.domUtils.showErrorModal('Error getting learning plan data.');
}
return Promise.reject(null);
});
}
/**
* Navigates to a particular competency.
*
* @param {number} competencyId
*/
openCompetency(competencyId: number): void {
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('AddonCompetencyCompetenciesPage', {competencyId, planId: this.planId});
}
/**
* Convenience function to get the status name translated.
*
* @param {number} status
* @return {any}
*/
protected getStatusName(status: number): any {
let statusTranslateName;
switch (status) {
case AddonCompetencyProvider.STATUS_DRAFT:
statusTranslateName = 'draft';
break;
case AddonCompetencyProvider.REVIEW_STATUS_IN_REVIEW:
statusTranslateName = 'inreview';
break;
case AddonCompetencyProvider.REVIEW_STATUS_WAITING_FOR_REVIEW:
statusTranslateName = 'waitingforreview';
break;
case AddonCompetencyProvider.STATUS_ACTIVE:
statusTranslateName = 'active';
break;
case AddonCompetencyProvider.STATUS_COMPLETE:
statusTranslateName = 'complete';
break;
default:
// We can use the current status name.
return status;
}
return this.translate.instant('addon.competency.planstatus' + statusTranslateName);
}
/**
* Refreshes the learning plan.
*
* @param {any} refresher Refresher.
*/
refreshLearningPlan(refresher: any): void {
this.competencyProvider.invalidateLearningPlan(this.planId).finally(() => {
this.fetchLearningPlan().finally(() => {
refresher.complete();
});
});
}
}

View File

@ -0,0 +1,22 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'addon.competency.userplans' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<core-split-view>
<ion-content>
<ion-refresher [enabled]="plansLoaded" (ionRefresh)="refreshLearningPlans($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="plansLoaded">
<core-empty-box *ngIf="plans.length == 0" icon="mapr" [message]="'addon.competency.noplanswerecreated' | translate">
</core-empty-box>
<ion-list *ngIf="plans.length > 0" no-margin>
<a ion-item text-wrap *ngFor="let plan of plans" [title]="plan.name" (click)="openPlan(plan.id)" [class.core-split-item-selected]="plan.id == planId">
<h2>{{ plan.name }}</h2>
<p *ngIf="plan.duedate > 0">{{ 'addon.competency.duedate' | translate }}: {{ plan.duedate | coreToLocaleString }}</p>
</a>
</ion-list>
</core-loading>
</ion-content>
</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 { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { AddonCompetencyPlanListPage } from './planlist';
@NgModule({
declarations: [
AddonCompetencyPlanListPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonCompetencyPlanListPage),
TranslateModule.forChild()
],
})
export class AddonCompetencyPlanListPageModule {}

View File

@ -0,0 +1,103 @@
// (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, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
/**
* Page that displays the list of learning plans.
*/
@IonicPage({ segment: 'addon-competency-planlist' })
@Component({
selector: 'page-addon-competency-planlist',
templateUrl: 'planlist.html',
})
export class AddonCompetencyPlanListPage {
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
protected userId: number;
protected planId: number;
plansLoaded = false;
plans = [];
constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider,
private competencyProvider: AddonCompetencyProvider) {
this.userId = navParams.get('userId');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
if (this.planId) {
// There is a learning plan to load.
this.openPlan(this.planId);
}
this.fetchLearningPlans().then(() => {
if (!this.planId && this.splitviewCtrl.isOn() && this.plans.length > 0) {
// Take first and load it.
this.openPlan(this.plans[0].id);
}
}).finally(() => {
this.plansLoaded = true;
});
}
/**
* Fetches the learning plans and updates the view.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchLearningPlans(): Promise<void> {
return this.competencyProvider.getLearningPlans(this.userId).then((plans) => {
this.plans = plans;
}).catch((message) => {
if (message) {
this.domUtils.showErrorModal(message);
} else {
this.domUtils.showErrorModal('Error getting learning plans data.');
}
return Promise.reject(null);
});
}
/**
* Refreshes the learning plans.
*
* @param {any} refresher Refresher.
*/
refreshLearningPlans(refresher: any): void {
this.competencyProvider.invalidateLearningPlans(this.userId).finally(() => {
this.fetchLearningPlans().finally(() => {
refresher.complete();
});
});
}
/**
* Opens a learning plan.
*
* @param {number} planId Learning plan to load.
*/
openPlan(planId: number): void {
this.planId = planId;
this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId });
}
}

View File

@ -0,0 +1,572 @@
// (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 { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites';
import { CoreUserProvider } from '../../../core/user/providers/user';
/**
* Service to handle caompetency learning plans.
*/
@Injectable()
export class AddonCompetencyProvider {
static STATUS_DRAFT = 0;
static STATUS_ACTIVE = 1;
static STATUS_COMPLETE = 2;
static STATUS_WAITIN_GFOR_REVIEW = 3;
static STATUS_IN_REVIEW = 4;
static REVIEW_STATUS_IDLE = 0;
static REVIEW_STATUS_WAITING_FOR_REVIEW = 1;
static REVIEW_STATUS_IN_REVIEW = 2;
protected ROOT_CACHE_KEY = 'mmaCompetency:';
protected logger;
constructor(loggerProvider: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
this.logger = loggerProvider.getInstance('AddonCompetencyProvider');
}
/**
* Get cache key for user learning plans data WS calls.
*
* @param {number} userId User ID.
* @return {string} Cache key.
*/
protected getLearningPlansCacheKey(userId: number): string {
return this.ROOT_CACHE_KEY + 'userplans:' + userId;
}
/**
* Get cache key for learning plan data WS calls.
*
* @param {number} planId Plan ID.
* @return {string} Cache key.
*/
protected getLearningPlanCacheKey(planId: number): string {
return this.ROOT_CACHE_KEY + 'learningplan:' + planId;
}
/**
* Get cache key for competency in plan data WS calls.
*
* @param {number} planId Plan ID.
* @param {number} competencyId Competency ID.
* @return {string} Cache key.
*/
protected getCompetencyInPlanCacheKey(planId: number, competencyId: number): string {
return this.ROOT_CACHE_KEY + 'plancompetency:' + planId + ':' + competencyId;
}
/**
* Get cache key for competency in course data WS calls.
*
* @param {number} courseId Course ID.
* @param {number} competencyId Competency ID.
* @param {number} userId User ID.
* @return {string} Cache key.
*/
protected getCompetencyInCourseCacheKey(courseId: number, competencyId: number, userId: number): string {
return this.ROOT_CACHE_KEY + 'coursecompetency:' + userId + ':' + courseId + ':' + competencyId;
}
/**
* Get cache key for competency summary data WS calls.
*
* @param {number} competencyId Competency ID.
* @param {number} userId User ID.
* @return {string} Cache key.
*/
protected getCompetencySummaryCacheKey(competencyId: number, userId: number): string {
return this.ROOT_CACHE_KEY + 'competencysummary:' + userId + ':' + competencyId;
}
/**
* Get cache key for course competencies data WS calls.
*
* @param {number} courseId Course ID.
* @return {string} Cache key.
*/
protected getCourseCompetenciesCacheKey(courseId: number): string {
return this.ROOT_CACHE_KEY + 'coursecompetencies:' + courseId;
}
/**
* Check if competency learning plans WS is available.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} True if competency learning plans WS is available, false otherwise.
*/
isPluginEnabled(siteId?: string): Promise<boolean> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
if (site.wsAvailable('core_competency_list_course_competencies') && site.wsAvailable('tool_lp_data_for_plans_page')) {
return this.getLearningPlans(0, siteId);
}
return false;
});
}
/**
* Returns whether competencies are enabled.
*
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} competencies if enabled for the given course, false otherwise.
*/
isPluginForCourseEnabled(courseId: number, siteId?: string): Promise<any> {
if (!this.sitesProvider.isLoggedIn()) {
return Promise.resolve(false);
}
if (!this.isPluginEnabled(siteId)) {
return Promise.resolve(false);
}
return this.getCourseCompetencies(courseId, 0, siteId).catch(() => {
return false;
});
}
/**
* Get plans for a certain user.
*
* @param {number} [userId] ID of the user. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the plans are retrieved.
*/
getLearningPlans(userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
this.logger.debug('Get plans for user ' + userId);
const params = {
userid: userId
},
preSets = {
cacheKey: this.getLearningPlansCacheKey(userId)
};
return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => {
if (response.plans) {
return response.plans;
}
return Promise.reject(null);
});
});
}
/**
* Get a certain plan.
*
* @param {number} planId ID of the plan.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the plans are retrieved.
*/
getLearningPlan(planId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
this.logger.debug('Get plan ' + planId);
const params = {
planid: planId
},
preSets = {
cacheKey: this.getLearningPlanCacheKey(planId)
};
return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => {
if (response.plan) {
return response;
}
return Promise.reject(null);
});
});
}
/**
* Get a certain competency in a plan.
*
* @param {number} planId ID of the plan.
* @param {number} competencyId ID of the competency.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the plans are retrieved.
*/
getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId);
const params = {
planid: planId,
competencyid: competencyId
},
preSets = {
cacheKey: this.getCompetencyInPlanCacheKey(planId, competencyId)
};
return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => {
if (response.usercompetencysummary) {
return response;
}
return Promise.reject(null);
});
});
}
/**
* Get a certain competency in a course.
*
* @param {number} courseId ID of the course.
* @param {number} competencyId ID of the competency.
* @param {number} [userId] ID of the user. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the plans are retrieved.
*/
getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
this.logger.debug('Get competency ' + competencyId + ' in course ' + courseId);
const params = {
courseid: courseId,
competencyid: competencyId,
userid: userId
},
preSets = {
cacheKey: this.getCompetencyInCourseCacheKey(courseId, competencyId, userId)
};
return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets).then((response) => {
if (response.usercompetencysummary) {
return response;
}
return Promise.reject(null);
});
});
}
/**
* Get a certain competency summary.
*
* @param {number} competencyId ID of the competency.
* @param {number} [userId] ID of the user. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the plans are retrieved.
*/
getCompetencySummary(competencyId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
this.logger.debug('Get competency ' + competencyId + ' summary for user' + userId);
const params = {
competencyid: competencyId,
userid: userId
},
preSets = {
cacheKey: this.getCompetencySummaryCacheKey(competencyId, userId)
};
return site.read('tool_lp_data_for_user_competency_summary', params, preSets).then((response) => {
if (response.competency) {
return response.competency;
}
return Promise.reject(null);
});
});
}
/**
* Get an specific competency summary.
*
* @param {number} courseId ID of the course.
* @param {number} [userId] ID of the user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the course competencies are retrieved.
*/
getCourseCompetencies(courseId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
this.logger.debug('Get course competencies for course ' + courseId);
const params = {
courseid: courseId
},
preSets = {
cacheKey: this.getCourseCompetenciesCacheKey(courseId)
};
return site.read('tool_lp_data_for_course_competencies_page', params, preSets).then((response) => {
if (response.competencies) {
return response;
}
return Promise.reject(null);
});
}).then((response) => {
if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) {
return response;
}
const promises = response.competencies.map((competency) =>
this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId)
);
return Promise.all(promises).then((responses: any[]) => {
responses.forEach((resp, index) => {
response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse;
});
return response;
});
});
}
/**
* Invalidates User Learning Plans data.
*
* @param {number} [userId] ID of the user. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateLearningPlans(userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getLearningPlansCacheKey(userId));
});
}
/**
* Invalidates Learning Plan data.
*
* @param {number} planId ID of the plan.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateLearningPlan(planId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getLearningPlanCacheKey(planId));
});
}
/**
* Invalidates Competency in Plan data.
*
* @param {number} planId ID of the plan.
* @param {number} competencyId ID of the competency.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getCompetencyInPlanCacheKey(planId, competencyId));
});
}
/**
* Invalidates Competency in Course data.
*
* @param {number} courseId ID of the course.
* @param {number} competencyId ID of the competency.
* @param {number} [userId] ID of the user. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCompetencyInCourseCacheKey(courseId, competencyId, userId));
});
}
/**
* Invalidates Competency Summary data.
*
* @param {number} competencyId ID of the competency.
* @param {number} [userId] ID of the user. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateCompetencySummary(competencyId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCompetencySummaryCacheKey(competencyId, userId));
});
}
/**
* Invalidates Course Competencies data.
*
* @param {number} courseId ID of the course.
* @param {number} [userId] ID of the user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateCourseCompetencies(courseId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getCourseCompetenciesCacheKey(courseId));
}).then((response) => {
if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) {
return;
}
/* Competencies for other users are fetched with getCompetencyInCourse (and saved in their own cache).
We need to fecth the list of competencies to know which ones to invalidate. We can pass 0 as userId
to getCourseCompetencies, we just need the competency IDs and this way we avid extra WS calls. */
return this.getCourseCompetencies(courseId, 0, siteId).then((competencies) => {
const promises = competencies.competencies.map((competency) => {
return this.invalidateCompetencyInCourse(courseId, competency.competency.id, userId, siteId);
});
return Promise.all(promises);
});
});
}
/**
* Report the competency as being viewed in plan.
*
* @param {number} planId ID of the plan.
* @param {number} competencyId ID of the competency.
* @param {number} planStatus Current plan Status to decide what action should be logged.
* @param {number} [userId] User ID. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, userId?: number, siteId?: string)
: Promise<any> {
if (planId && competencyId) {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const params = {
planid: planId,
competencyid: competencyId,
userid: userId
},
preSets = {
typeExpected: 'boolean'
};
if (planStatus == AddonCompetencyProvider.STATUS_COMPLETE) {
return site.write('core_competency_user_competency_plan_viewed', params, preSets);
} else {
return site.write('core_competency_user_competency_viewed_in_plan', params, preSets);
}
});
}
return Promise.reject(null);
}
/**
* Report the competency as being viewed in course.
*
* @param {number} courseId ID of the course.
* @param {number} competencyId ID of the competency.
* @param {number} [userId] User ID. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logCompetencyInCourseView(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise<any> {
if (courseId && competencyId) {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const params = {
courseid: courseId,
competencyid: competencyId,
userid: userId
};
const preSets = {
typeExpected: 'boolean'
};
return site.write('core_competency_user_competency_viewed_in_course', params, preSets);
});
}
return Promise.reject(null);
}
/**
* Report the competency as being viewed.
*
* @param {number} competencyId ID of the competency.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logCompetencyView(competencyId: number, siteId?: string): Promise<any> {
if (competencyId) {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
id: competencyId,
};
const preSets = {
typeExpected: 'boolean'
};
return site.write('core_competency_competency_viewed', params, preSets);
});
}
return Promise.reject(null);
}
}

View File

@ -0,0 +1,109 @@
// (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 { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '../../../core/course/providers/options-delegate';
import { CoreCourseProvider } from '../../../core/course/providers/course';
import { AddonCompetencyCourseComponent } from '../components/course/course';
import { AddonCompetencyProvider } from '../providers/competency';
/**
* Course nav handler.
*/
@Injectable()
export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHandler {
name = 'AddonCompetency';
priority = 700;
protected coursesNavEnabledCache = {};
constructor(private competencyProvider: AddonCompetencyProvider) {}
/**
* Clear courses nav cache.
*/
clearCoursesNavCache(): void {
this.coursesNavEnabledCache = {};
}
/**
* Whether or not the handler is enabled ona site level.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
return this.competencyProvider.isPluginEnabled();
}
/**
* Whether or not the handler is enabled for a certain course.
*
* @param {number} courseId The course ID.
* @param {any} accessData Access type and data. Default, 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 {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
return false; // Not enabled for guests.
}
if (navOptions && typeof navOptions.competencies != 'undefined') {
return navOptions.competencies;
}
if (typeof this.coursesNavEnabledCache[courseId] != 'undefined') {
return this.coursesNavEnabledCache[courseId];
}
return this.competencyProvider.isPluginForCourseEnabled(courseId).then((competencies) => {
const enabled = competencies ? !competencies.canmanagecoursecompetencies : false;
this.coursesNavEnabledCache[courseId] = enabled;
return enabled;
});
}
/**
* Returns the data needed to render the handler.
*
* @param {number} courseId The course ID.
* @return {CoreCourseOptionsHandlerData} Data.
*/
getDisplayData(courseId: number): CoreCourseOptionsHandlerData {
return {
title: 'addon.competency.competencies',
class: 'addon-competency-course-handler',
component: AddonCompetencyCourseComponent
};
}
/**
* Should invalidate the data to determine if the handler is enabled for a certain course.
*
* @param {number} courseId The course ID.
* @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<any>} Promise resolved when done.
*/
invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise<any> {
if (navOptions && typeof navOptions.competencies != 'undefined') {
// No need to invalidate anything.
return Promise.resolve();
}
return this.competencyProvider.invalidateCourseCompetencies(courseId);
}
}

View File

@ -0,0 +1,46 @@
// (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 { CoreSitesProvider } from '../../../providers/sites';
import { CoreUserProvider } from '../../../core/user/providers/user';
/**
* Service that provides some features regarding the user profile.
*/
@Injectable()
export class AddonCompetencyHelperProvider {
constructor(private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) {
}
/**
* Convenient helper to get the user profile image.
*
* @param {number} userId User Id
* @return {Promise<any>} User profile Image URL or true if default icon.
*/
getProfile(userId: number): Promise<any> {
if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) {
return Promise.resolve(false);
}
// Get the user profile to retrieve the user image.
return this.userProvider.getProfile(userId, null, true).then((user) => {
user.profileimageurl = user.profileimageurl || true;
return user;
});
}
}

View File

@ -0,0 +1,60 @@
// (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 { AddonCompetencyProvider } from './competency';
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate';
/**
* Handler to inject an option into main menu.
*/
@Injectable()
export class AddonCompetencyMainMenuHandler implements CoreMainMenuHandler {
name = 'AddonCompetency';
priority = 900;
constructor(private competencyProvider: AddonCompetencyProvider) { }
/**
* Check if the handler is enabled on a site level.
*
* @return {boolean} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
return this.competencyProvider.isPluginEnabled().then((enabled) => {
if (!enabled) {
return false;
}
// Check the user has at least one learn plan available.
return this.competencyProvider.getLearningPlans().then((plans) => {
return plans.length > 0;
});
});
}
/**
* Returns the data needed to render the handler.
*
* @return {CoreMainMenuHandlerData} Data needed to render the handler.
*/
getDisplayData(): CoreMainMenuHandlerData {
return {
icon: 'map',
title: 'addon.competency.myplans',
page: 'AddonCompetencyPlanListPage',
class: 'addon-competency-handler'
};
}
}

View File

@ -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 { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../../core/user/providers/user-delegate';
import { CoreSitesProvider } from '../../../providers/sites';
import { CoreContentLinksHelperProvider } from '../../../core/contentlinks/providers/helper';
import { AddonCompetencyProvider } from './competency';
/**
* Profile competencies handler.
*/
@Injectable()
export class AddonCompetencyUserHandler implements CoreUserProfileHandler {
name = 'AddonCompetency';
priority = 900;
type = CoreUserDelegate.TYPE_NEW_PAGE;
participantsNavEnabledCache = {};
usersNavEnabledCache = {};
constructor(private linkHelper: CoreContentLinksHelperProvider, protected sitesProvider: CoreSitesProvider,
private competencyProvider: AddonCompetencyProvider) {
}
/**
* Clear users nav cache.
*/
clearUsersNavCache(): void {
this.participantsNavEnabledCache = {};
this.usersNavEnabledCache = {};
}
/**
* Whether or not the handler is enabled on a site level.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
return this.competencyProvider.isPluginEnabled();
}
/**
* Check if handler is enabled for this user in this context.
*
* @param {any} user User to check.
* @param {number} courseId Course ID.
* @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions.
* @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions.
* @return {boolean|Promise<boolean>} Promise resolved with true if enabled, resolved with false otherwise.
*/
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
if (courseId) {
const cacheKey = courseId + '.' + user.id;
// Link on a user course profile.
if (typeof this.participantsNavEnabledCache[cacheKey] != 'undefined') {
return this.participantsNavEnabledCache[cacheKey];
}
return this.competencyProvider.getCourseCompetencies(courseId, user.id).then((response) => {
const enabled = response.competencies.length > 0;
this.participantsNavEnabledCache[cacheKey] = enabled;
return enabled;
}).catch((message) => {
this.participantsNavEnabledCache[cacheKey] = false;
return false;
});
} else {
// Link on a user site profile.
if (typeof this.usersNavEnabledCache[user.id] != 'undefined') {
return this.usersNavEnabledCache[user.id];
}
return this.competencyProvider.getLearningPlans(user.id).then((plans) => {
// Check the user has at least one learn plan available.
const enabled = plans.length > 0;
this.usersNavEnabledCache[user.id] = enabled;
return enabled;
});
}
}
/**
* Returns the data needed to render the handler.
*
* @return {CoreUserProfileHandlerData} Data needed to render the handler.
*/
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData {
if (courseId) {
return {
icon: 'ribbon',
title: 'addon.competency.competencies',
class: 'addon-competency-handler',
action: ($event, navCtrl, user, courseId): void => {
event.preventDefault();
event.stopPropagation();
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.linkHelper.goInSite(navCtrl, 'AddonCompetencyCourseCompetenciesPage', {courseId, userId: user.id});
}
};
} else {
return {
icon: 'map',
title: 'addon.competency.learningplans',
class: 'addon-competency-handler',
action: ($event, navCtrl, user, courseId): void => {
event.preventDefault();
event.stopPropagation();
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.linkHelper.goInSite(navCtrl, 'AddonCompetencyPlanListPage', {userId: user.id});
}
};
}
}
}

View File

@ -70,6 +70,7 @@ import { CoreCompileModule } from '@core/compile/compile.module';
// Addon modules.
import { AddonCalendarModule } from '@addon/calendar/calendar.module';
import { AddonCompetencyModule } from '../addon/competency/competency.module';
import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module';
import { AddonFilesModule } from '@addon/files/files.module';
import { AddonModBookModule } from '@addon/mod/book/book.module';
@ -147,6 +148,7 @@ export const CORE_PROVIDERS: any[] = [
CoreSitePluginsModule,
CoreCompileModule,
AddonCalendarModule,
AddonCompetencyModule,
AddonUserProfileFieldModule,
AddonFilesModule,
AddonModBookModule,