MOBILE-2302 courses: Implement categories and available courses

main
Dani Palou 2017-12-15 08:03:33 +01:00
parent 2b8b5b8b87
commit cacab75855
7 changed files with 324 additions and 0 deletions

View File

@ -0,0 +1,16 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.courses.availablecourses' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="coursesLoaded" (ionRefresh)="refreshCourses($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="coursesLoaded">
<div *ngIf="courses.length > 0">
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
</div>
<core-empty-box *ngIf="!courses.length" icon="ionic" [message]="'core.courses.nocourses' | translate"></core-empty-box>
</core-loading>
</ion-content>

View File

@ -0,0 +1,33 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreCoursesAvailableCoursesPage } from './available-courses';
import { CoreComponentsModule } from '../../../../components/components.module';
import { CoreCoursesComponentsModule } from '../../components/components.module';
@NgModule({
declarations: [
CoreCoursesAvailableCoursesPage,
],
imports: [
CoreComponentsModule,
CoreCoursesComponentsModule,
IonicPageModule.forChild(CoreCoursesAvailableCoursesPage),
TranslateModule.forChild()
],
})
export class CoreCoursesAvailableCoursesPageModule {}

View File

@ -0,0 +1,76 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { CoreSitesProvider } from '../../../../providers/sites';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreCoursesProvider } from '../../providers/courses';
/**
* Page that displays available courses in current site.
*/
@IonicPage()
@Component({
selector: 'page-core-courses-available-courses',
templateUrl: 'available-courses.html',
})
export class CoreCoursesAvailableCoursesPage {
courses: any[] = [];
coursesLoaded: boolean;
constructor(private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider,
private sitesProvider: CoreSitesProvider) {}
/**
* View loaded.
*/
ionViewDidLoad() {
this.loadCourses().finally(() => {
this.coursesLoaded = true;
});
}
/**
* Load the courses.
*/
protected loadCourses() {
const frontpageCourseId = this.sitesProvider.getCurrentSite().getSiteHomeId();
return this.coursesProvider.getCoursesByField().then((courses) => {
this.courses = courses.filter((course) => {
return course.id != frontpageCourseId;
});
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
});
}
/**
* Refresh the courses.
*
* @param {any} refresher Refresher.
*/
refreshCourses(refresher: any) {
let promises = [];
promises.push(this.coursesProvider.invalidateUserCourses());
promises.push(this.coursesProvider.invalidateCoursesByField());
Promise.all(promises).finally(() => {
this.loadCourses().finally(() => {
refresher.complete();
});
});
};
}

View File

@ -0,0 +1,39 @@
<ion-header>
<ion-navbar>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="categoriesLoaded" (ionRefresh)="refreshCategories($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="categoriesLoaded">
<ion-item *ngIf="currentCategory" text-wrap>
<ion-icon name="folder" item-start></ion-icon>
<h2><core-format-text [text]="currentCategory.name"></core-format-text></h2>
</ion-item>
<ion-item text-wrap *ngIf="currentCategory && currentCategory.description">
<core-format-text [text]="currentCategory.description" maxHeight="60"></core-format-text>
</ion-item>
<div *ngIf="categories.length > 0">
<ion-item-divider color="light">{{ 'core.courses.categories' | translate }}</ion-item-divider>
<section *ngFor="let category of categories">
<a ion-item text-wrap (click)="openCategory(category.id)" [title]="category.name">
<ion-icon name="folder" item-start></ion-icon>
<h2><core-format-text [text]="category.name"></core-format-text></h2>
<ion-badge item-end *ngIf="category.coursecount > 0" color="light">{{category.coursecount}}</ion-badge>
<ion-icon item-end name="arrow-forward" md="ios-arrow-forward" class="icon-accessory"></ion-icon>
</a>
</section>
</div>
<div *ngIf="courses.length > 0">
<ion-item-divider color="light">{{ 'core.courses.courses' | translate }}</ion-item-divider>
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
</div>
<core-empty-box *ngIf="!categories.length && !courses.length" icon="ionic" [message]="'core.courses.nocoursesyet' | translate">
<p *ngIf="searchEnabled">{{ 'core.courses.searchcoursesadvice' | translate }}</p>
</core-empty-box>
</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 { CoreCoursesCategoriesPage } from './categories';
import { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CoreCoursesComponentsModule } from '../../components/components.module';
@NgModule({
declarations: [
CoreCoursesCategoriesPage,
],
imports: [
CoreComponentsModule,
CoreCoursesComponentsModule,
CoreDirectivesModule,
IonicPageModule.forChild(CoreCoursesCategoriesPage),
TranslateModule.forChild()
],
})
export class CoreCoursesCategoriesPageModule {}

View File

@ -0,0 +1,122 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '../../../../providers/sites';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
import { CoreCoursesProvider } from '../../providers/courses';
/**
* Page that displays a list of categories and the courses in the current category if any.
*/
@IonicPage()
@Component({
selector: 'page-core-courses-categories',
templateUrl: 'categories.html',
})
export class CoreCoursesCategoriesPage {
title: string;
currentCategory: any;
categories: any[] = [];
courses: any[] = [];
categoriesLoaded: boolean;
protected categoryId: number;
constructor(private navCtrl: NavController, navParams: NavParams, private coursesProvider: CoreCoursesProvider,
private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider, translate: TranslateService,
private sitesProvider: CoreSitesProvider) {
this.categoryId = navParams.get('categoryId') || 0;
this.title = translate.instant('core.courses.categories');
}
/**
* View loaded.
*/
ionViewDidLoad() {
this.fetchCategories().finally(() => {
this.categoriesLoaded = true;
});
}
/**
* Fetch the categories.
*/
protected fetchCategories() {
return this.coursesProvider.getCategories(this.categoryId, true).then((cats) => {
this.currentCategory = undefined;
cats.forEach((cat, index) => {
if (cat.id == this.categoryId) {
this.currentCategory = cat;
// Delete current Category to avoid problems with the formatTree.
delete cats[index];
}
});
// Sort by depth and sortorder to avoid problems formatting Tree.
cats.sort((a,b) => {
if (a.depth == b.depth) {
return (a.sortorder > b.sortorder) ? 1 : ((b.sortorder > a.sortorder) ? -1 : 0);
}
return a.depth > b.depth ? 1 : -1;
});
this.categories = this.utils.formatTree(cats, 'parent', 'id', this.categoryId);
if (this.currentCategory) {
this.title = this.currentCategory.name;
return this.coursesProvider.getCoursesByField('category', this.categoryId).then((courses) => {
this.courses = courses;
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
});
}
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
});
}
/**
* Refresh the categories.
*
* @param {any} refresher Refresher.
*/
refreshCategories(refresher: any) {
let promises = [];
promises.push(this.coursesProvider.invalidateUserCourses());
promises.push(this.coursesProvider.invalidateCategories(this.categoryId, true));
promises.push(this.coursesProvider.invalidateCoursesByField('category', this.categoryId));
promises.push(this.sitesProvider.getCurrentSite().invalidateConfig());
Promise.all(promises).finally(() => {
this.fetchCategories().finally(() => {
refresher.complete();
});
});
}
/**
* Open a category.
*
* @param {number} categoryId The category ID.
*/
openCategory(categoryId: number) {
this.navCtrl.push('CoreCoursesCategoriesPage', {categoryId: categoryId});
}
}

View File

@ -151,6 +151,7 @@ export class CoreFormatTextDirective implements OnChanges {
*/
protected formatAndRenderContents() : void {
if (!this.text) {
this.element.innerHTML = ''; // Remove current contents.
this.finishRender();
return;
}
@ -158,6 +159,8 @@ export class CoreFormatTextDirective implements OnChanges {
this.text = this.text.trim();
this.formatContents().then((div: HTMLElement) => {
this.element.innerHTML = ''; // Remove current contents.
if (this.maxHeight && div.innerHTML != "") {
// Move the children to the current element to be able to calculate the height.
// @todo: Display the element?