MOBILE-2344 core: Scroll to module when link clicked

main
Dani Palou 2018-01-31 11:37:42 +01:00
parent f2a5bc0eb6
commit f807907ccb
8 changed files with 68 additions and 32 deletions

View File

@ -13,6 +13,7 @@
// limitations under the License.
import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChange, Output, EventEmitter } from '@angular/core';
import { Content } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '../../../../providers/events';
import { CoreSitesProvider } from '../../../../providers/sites';
@ -42,6 +43,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
@Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
@Input() initialSectionId?: number; // The section to load first (by ID).
@Input() initialSectionNumber?: number; // The section to load first (by number).
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
@Output() completionChanged?: EventEmitter<void>; // Will emit an event when any module completion changes.
// All the possible component classes.
@ -64,7 +66,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService,
private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider,
eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider,
eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private content: Content,
prefetchDelegate: CoreCourseModulePrefetchDelegate) {
this.selectOptions.title = translate.instant('core.course.sections');
@ -132,7 +134,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
// We have an input indicating the section ID to load. Search the section.
for (let i = 0; i < this.sections.length; i++) {
const section = this.sections[i];
if (section.id == this.initialSectionId || section.section == this.initialSectionNumber) {
if ((section.id && section.id == this.initialSectionId) ||
(section.section && section.section == this.initialSectionNumber)) {
this.loaded = true;
this.sectionChanged(section);
break;
@ -212,6 +215,12 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
const previousValue = this.selectedSection;
this.selectedSection = newSection;
this.data.section = this.selectedSection;
if (this.moduleId && typeof previousValue == 'undefined') {
setTimeout(() => {
this.domUtils.scrollToElementBySelector(this.content, '#core-course-module-' + this.moduleId);
}, 200);
}
}
/**

View File

@ -21,7 +21,7 @@
</ion-refresher>
<core-loading [hideUntil]="dataLoaded">
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber" [downloadEnabled]="downloadEnabled" (completionChanged)="onCompletionChange()"></core-course-format>
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber" [downloadEnabled]="downloadEnabled" [moduleId]="moduleId" (completionChanged)="onCompletionChange()"></core-course-format>
</core-loading>
</ion-content>
</ng-template>

View File

@ -50,12 +50,14 @@ export class CoreCourseSectionPage implements OnDestroy {
prefetchCourseData = {
prefetchCourseIcon: 'spinner'
};
moduleId: number;
protected module: any;
protected completionObserver;
protected courseStatusObserver;
protected isDestroyed = false;
constructor(private navParams: NavParams, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider,
constructor(navParams: NavParams, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider,
private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate,
private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider,
private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider,
@ -64,6 +66,7 @@ export class CoreCourseSectionPage implements OnDestroy {
this.course = navParams.get('course');
this.sectionId = navParams.get('sectionId');
this.sectionNumber = navParams.get('sectionNumber');
this.module = navParams.get('module');
this.handlerData.courseId = this.course.id;
// Get the title to display. We dont't have sections yet.
@ -88,9 +91,9 @@ export class CoreCourseSectionPage implements OnDestroy {
*/
ionViewDidLoad(): void {
const module = this.navParams.get('module');
if (module) {
this.courseHelper.openModule(this.navCtrl, module, this.course.id, this.sectionId);
if (this.module) {
this.moduleId = this.module.id;
this.courseHelper.openModule(this.navCtrl, this.module, this.course.id, this.sectionId);
}
this.loadData().finally(() => {

View File

@ -641,13 +641,20 @@ export class CoreCourseHelperProvider {
* @param {any} module The module to open.
* @param {number} courseId The course ID of the module.
* @param {number} [sectionId] The section ID of the module.
* @param {boolean} True if module can be opened, false otherwise.
*/
openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number): void {
openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number): boolean {
if (!module.handlerData) {
module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId);
}
module.handlerData.action(new Event('click'), navCtrl, module, courseId, { animate: false });
if (module.handlerData && module.handlerData.action) {
module.handlerData.action(new Event('click'), navCtrl, module, courseId, { animate: false });
return true;
}
return false;
}
/**

View File

@ -205,7 +205,7 @@ export class CoreLoginEmailSignupPage {
create(): void {
if (!this.signupForm.valid) {
// Form not valid. Scroll to the first element with errors.
if (!this.domUtils.scrollToInputError(this.content, document.body)) {
if (!this.domUtils.scrollToInputError(this.content)) {
// Input not found, show an error modal.
this.domUtils.showErrorModal('core.errorinvalidform', true);
}

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
import { Platform, NavController } from 'ionic-angular';
import { Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core';
import { Platform, NavController, Content } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '../providers/app';
import { CoreFilepoolProvider } from '../providers/filepool';
@ -62,7 +62,8 @@ export class CoreFormatTextDirective implements OnChanges {
private textUtils: CoreTextUtilsProvider, private translate: TranslateService, private platform: Platform,
private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private loggerProvider: CoreLoggerProvider,
private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider,
private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController) {
private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController,
@Optional() private content: Content) {
this.element = element.nativeElement;
this.element.classList.add('opacity-hide'); // Hide contents until they're treated.
this.afterRender = new EventEmitter();
@ -280,7 +281,7 @@ export class CoreFormatTextDirective implements OnChanges {
anchors.forEach((anchor) => {
// Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
const linkDir = new CoreLinkDirective(anchor, this.domUtils, this.utils, this.sitesProvider, this.urlUtils,
this.contentLinksHelper, this.navCtrl);
this.contentLinksHelper, this.navCtrl, this.content);
linkDir.capture = true;
linkDir.ngOnInit();

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import { NavController } from 'ionic-angular';
import { NavController, Content } from 'ionic-angular';
import { CoreSitesProvider } from '../providers/sites';
import { CoreDomUtilsProvider } from '../providers/utils/dom';
import { CoreUrlUtilsProvider } from '../providers/utils/url';
@ -38,8 +38,9 @@ export class CoreLinkDirective implements OnInit {
protected element: HTMLElement;
constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider,
private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider,
private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController) {
private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider,
private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController,
private content: Content) {
// This directive can be added dynamically. In that case, the first param is the anchor HTMLElement.
this.element = element.nativeElement || element;
}
@ -93,8 +94,7 @@ export class CoreLinkDirective implements OnInit {
// $location.url(href);
} else {
// Look for id or name.
const scrollEl = <HTMLElement> this.domUtils.closest(this.element, 'scroll-content');
this.domUtils.scrollToElement(scrollEl, document.body, '#' + href + ', [name=\'' + href + '\']');
this.domUtils.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']');
}
} else if (href.indexOf(contentLinksScheme) === 0) {
// Link should be treated by Custom URL Scheme. Encode the right part, otherwise ':' is removed in iOS.

View File

@ -584,22 +584,39 @@ export class CoreDomUtilsProvider {
}
/**
* Scroll to a certain element inside another element.
* Scroll to a certain element.
*
* @param {Content|HTMLElement} scrollEl The content that must be scrolled.
* @param {HTMLElement} container Element to search in.
* @param {string} [selector] Selector to find the element to scroll to. If not defined, scroll to the container.
* @param {Content} content The content that must be scrolled.
* @param {HTMLElement} element The element to scroll to.
* @param {string} [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content.
* @return {boolean} True if the element is found, false otherwise.
*/
scrollToElement(scrollEl: Content | HTMLElement, container: HTMLElement, selector?: string, scrollParentClass?: string)
: boolean {
const position = this.getElementXY(container, selector, scrollParentClass);
scrollToElement(content: Content, element: HTMLElement, scrollParentClass?: string): boolean {
const position = this.getElementXY(element, undefined, scrollParentClass);
if (!position) {
return false;
}
scrollEl.scrollTo(position[0], position[1]);
content.scrollTo(position[0], position[1]);
return true;
}
/**
* Scroll to a certain element using a selector to find it.
*
* @param {Content} content The content that must be scrolled.
* @param {string} selector Selector to find the element to scroll to.
* @param {string} [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content.
* @return {boolean} True if the element is found, false otherwise.
*/
scrollToElementBySelector(content: Content, selector: string, scrollParentClass?: string): boolean {
const position = this.getElementXY(content.getScrollElement(), selector, scrollParentClass);
if (!position) {
return false;
}
content.scrollTo(position[0], position[1]);
return true;
}
@ -607,17 +624,16 @@ export class CoreDomUtilsProvider {
/**
* Search for an input with error (core-input-error directive) and scrolls to it if found.
*
* @param {Content|HTMLElement} scrollEl The element that must be scrolled.
* @param {HTMLElement} container Element to search in.
* @param {Content} content The content that must be scrolled.
* @param [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content.
* @return {boolean} True if the element is found, false otherwise.
*/
scrollToInputError(scrollEl: Content | HTMLElement, container: HTMLElement, scrollParentClass?: string): boolean {
if (!scrollEl) {
scrollToInputError(content: Content, scrollParentClass?: string): boolean {
if (!content) {
return false;
}
return this.scrollToElement(scrollEl, container, '.core-input-error', scrollParentClass);
return this.scrollToElementBySelector(content, '.core-input-error', scrollParentClass);
}
/**