forked from CIT/Vmeda.Online
		
	MOBILE-3639 choice: Implement index page
This commit is contained in:
		
							parent
							
								
									18757924bb
								
							
						
					
					
						commit
						53d808ad76
					
				
							
								
								
									
										38
									
								
								src/addons/mod/choice/choice-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/addons/mod/choice/choice-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// 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 { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { AddonModChoiceComponentsModule } from './components/components.module';
 | 
			
		||||
import { AddonModChoiceIndexPage } from './pages/index/index';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: ':courseId/:cmId',
 | 
			
		||||
        component: AddonModChoiceIndexPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        AddonModChoiceComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModChoiceIndexPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModChoiceLazyModule {}
 | 
			
		||||
@ -13,19 +13,22 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { AddonModChoiceComponentsModule } from './components/components.module';
 | 
			
		||||
import { AddonModChoiceProvider } from './services/choice';
 | 
			
		||||
import { AddonModChoiceOfflineProvider } from './services/choice-offline';
 | 
			
		||||
import { AddonModChoiceSyncProvider } from './services/choice-sync';
 | 
			
		||||
import { OFFLINE_SITE_SCHEMA } from './services/database/choice';
 | 
			
		||||
import { AddonModChoiceIndexLinkHandler } from './services/handlers/index-link';
 | 
			
		||||
import { AddonModChoiceListLinkHandler } from './services/handlers/list-link';
 | 
			
		||||
import { AddonModChoiceModuleHandler } from './services/handlers/module';
 | 
			
		||||
import { AddonModChoiceModuleHandler, AddonModChoiceModuleHandlerService } from './services/handlers/module';
 | 
			
		||||
import { AddonModChoicePrefetchHandler } from './services/handlers/prefetch';
 | 
			
		||||
import { AddonModChoiceSyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
 | 
			
		||||
@ -35,8 +38,17 @@ export const ADDON_MOD_CHOICE_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonModChoiceSyncProvider,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: AddonModChoiceModuleHandlerService.PAGE_NAME,
 | 
			
		||||
        loadChildren: () => import('./choice-lazy.module').then(m => m.AddonModChoiceLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        AddonModChoiceComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								src/addons/mod/choice/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/addons/mod/choice/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// 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 { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
import { AddonModChoiceIndexComponent } from './index/index';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModChoiceIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModChoiceIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModChoiceComponentsModule {}
 | 
			
		||||
@ -0,0 +1,175 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
 | 
			
		||||
            [href]="externalUrl" iconAction="fas-external-link-alt">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
 | 
			
		||||
            iconAction="far-newspaper" (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline"  [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
 | 
			
		||||
<!-- Content. -->
 | 
			
		||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
 | 
			
		||||
 | 
			
		||||
    <core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
 | 
			
		||||
        contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
    </core-course-module-description>
 | 
			
		||||
 | 
			
		||||
    <!-- Activity availability messages -->
 | 
			
		||||
    <ion-card class="core-info-card" *ngIf="choiceNotOpenYet">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-info-circle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <p *ngIf="options.length">{{ 'addon.mod_choice.previewonly' | translate:{$a: openTimeReadable} }}</p>
 | 
			
		||||
                <p *ngIf="!options.length">{{ 'addon.mod_choice.notopenyet' | translate:{$a: openTimeReadable} }}</p>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <ion-card class="core-info-card" *ngIf="choiceClosed">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-info-circle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <p *ngIf="options.length">
 | 
			
		||||
                    {{ 'addon.mod_choice.yourselection' | translate }}
 | 
			
		||||
                    <core-format-text [text]="options[0].text" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
                        [courseId]="courseId">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </p>
 | 
			
		||||
                <p>{{ 'addon.mod_choice.expired' | translate:{$a: closeTimeReadable} }}</p>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <!-- Choice done in offline but not synchronized -->
 | 
			
		||||
    <ion-card class="core-warning-card" *ngIf="hasOffline">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label>{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <!-- Inform what will happen with the choices. -->
 | 
			
		||||
    <ion-card class="core-info-card" *ngIf="canEdit && publishInfo && options.length">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-info-circle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label>{{ publishInfo | translate }}</ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <!-- Choice options -->
 | 
			
		||||
    <ion-card *ngIf="options.length && choice">
 | 
			
		||||
        <ng-container *ngIf="choice.allowmultiple">
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngFor="let option of options">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}"></ng-container>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-checkbox slot="end" [(ngModel)]="option.checked" [disabled]="option.disabled || !canEdit"></ion-checkbox>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
        <ion-radio-group *ngIf="!choice.allowmultiple" [(ngModel)]="selectedOption.id">
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngFor="let option of options">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}"></ng-container>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-radio slot="end" [value]="option.id" [disabled]="option.disabled || !canEdit"></ion-radio>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-radio-group>
 | 
			
		||||
        <ion-button *ngIf="canEdit" expand="block" (click)="save()" [disabled]="!canSave()" class="ion-margin">
 | 
			
		||||
            {{ 'addon.mod_choice.savemychoice' | translate }}
 | 
			
		||||
        </ion-button>
 | 
			
		||||
        <ion-button *ngIf="canDelete" expand="block" color="light" (click)="delete()" class="ion-margin">
 | 
			
		||||
            {{ 'addon.mod_choice.removemychoice' | translate }}
 | 
			
		||||
        </ion-button>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <!-- Choice results -->
 | 
			
		||||
    <div *ngIf="canSeeResults && choice">
 | 
			
		||||
        <ion-item-divider>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <h2>{{ 'addon.mod_choice.responses' | translate }}</h2>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item-divider>
 | 
			
		||||
        <ion-grid class="ion-no-padding">
 | 
			
		||||
            <ion-row>
 | 
			
		||||
                <ion-col size="12" size-lg="5">
 | 
			
		||||
                    <ion-item class="ion-text-wrap core-warning-item" *ngIf="hasOffline">
 | 
			
		||||
                        <ion-icon slot="start" name="fas-exclamation-triangle" color="warning"></ion-icon>
 | 
			
		||||
                        <ion-label>{{ 'addon.mod_choice.resultsnotsynced' | translate }}</ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                    <ion-item>
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <core-chart type="pie" [data]="data" [labels]="labels" height="300" contextLevel="module"
 | 
			
		||||
                                [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
                            </core-chart>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                </ion-col>
 | 
			
		||||
                <ion-col *ngIf="choice.publish && results" size="12" size-lg="7">
 | 
			
		||||
                    <ion-item-group *ngFor="let result of results">
 | 
			
		||||
                        <ion-item-divider class="ion-text-wrap">
 | 
			
		||||
                            <ion-label>
 | 
			
		||||
                                <h2>
 | 
			
		||||
                                    <core-format-text [text]="result.text" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
                                        [courseId]="courseId">
 | 
			
		||||
                                    </core-format-text>
 | 
			
		||||
                                </h2>
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    {{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }}
 | 
			
		||||
                                    ({{ 'core.percentagenumber' | translate: {$a: result.percentageamountfixed} }})
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p *ngIf="choice.limitanswers && choice.showavailable">
 | 
			
		||||
                                    {{ 'addon.mod_choice.limita' | translate:{$a: result.maxanswer} }}
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </ion-label>
 | 
			
		||||
                        </ion-item-divider>
 | 
			
		||||
                        <ion-item *ngFor="let user of result.userresponses" core-user-link [courseId]="courseId"
 | 
			
		||||
                            [userId]="user.userid" [title]="user.fullname" class="ion-text-wrap">
 | 
			
		||||
                            <core-user-avatar [user]="user" slot="start" [courseId]="courseId"></core-user-avatar>
 | 
			
		||||
                            <ion-label><p>{{user.fullname}}</p></ion-label>
 | 
			
		||||
                        </ion-item>
 | 
			
		||||
                    </ion-item-group>
 | 
			
		||||
                </ion-col>
 | 
			
		||||
            </ion-row>
 | 
			
		||||
        </ion-grid>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <ion-card class="core-info-card" *ngIf="!canSeeResults && !choiceNotOpenYet">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-info-circle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label><p>{{ 'addon.mod_choice.noresultsviewable' | translate }}</p></ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
</core-loading>
 | 
			
		||||
 | 
			
		||||
<!-- Template to render a choice option label. -->
 | 
			
		||||
<ng-template #optionLabelTemplate let-option="option">
 | 
			
		||||
    <p>
 | 
			
		||||
        <core-format-text [text]="option.text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
        </core-format-text>
 | 
			
		||||
        <span *ngIf="choice!.limitanswers && option.countanswers >= option.maxanswers">
 | 
			
		||||
            {{ 'addon.mod_choice.full' | translate }}
 | 
			
		||||
        </span>
 | 
			
		||||
    </p>
 | 
			
		||||
    <ng-container *ngIf="choice!.limitanswers && choice!.showavailable">
 | 
			
		||||
        <p>{{ 'addon.mod_choice.responsesa' | translate:{$a: option.countanswers} }}</p>
 | 
			
		||||
        <p>{{ 'addon.mod_choice.limita' | translate:{$a: option.maxanswers} }}</p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ng-template>
 | 
			
		||||
							
								
								
									
										479
									
								
								src/addons/mod/choice/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								src/addons/mod/choice/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,479 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// 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, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
 | 
			
		||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModChoice,
 | 
			
		||||
    AddonModChoiceChoice,
 | 
			
		||||
    AddonModChoiceOption,
 | 
			
		||||
    AddonModChoiceProvider,
 | 
			
		||||
    AddonModChoiceResult,
 | 
			
		||||
} from '../../services/choice';
 | 
			
		||||
import { AddonModChoiceOffline } from '../../services/choice-offline';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModChoiceAutoSyncData,
 | 
			
		||||
    AddonModChoiceSync,
 | 
			
		||||
    AddonModChoiceSyncProvider,
 | 
			
		||||
    AddonModChoiceSyncResult,
 | 
			
		||||
} from '../../services/choice-sync';
 | 
			
		||||
import { AddonModChoicePrefetchHandler } from '../../services/handlers/prefetch';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a choice.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-choice-index',
 | 
			
		||||
    templateUrl: 'addon-mod-choice-index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    component = AddonModChoiceProvider.COMPONENT;
 | 
			
		||||
    moduleName = 'choice';
 | 
			
		||||
 | 
			
		||||
    choice?: AddonModChoiceChoice;
 | 
			
		||||
    options: AddonModChoiceOption[] = [];
 | 
			
		||||
    selectedOption: {id: number} = { id: -1 };
 | 
			
		||||
    choiceNotOpenYet = false;
 | 
			
		||||
    choiceClosed = false;
 | 
			
		||||
    canEdit = false;
 | 
			
		||||
    canDelete = false;
 | 
			
		||||
    canSeeResults = false;
 | 
			
		||||
    data: number[] = [];
 | 
			
		||||
    labels: string[] = [];
 | 
			
		||||
    results: AddonModChoiceResultFormatted[] = [];
 | 
			
		||||
    publishInfo?: string; // Message explaining the user what will happen with his choices.
 | 
			
		||||
    openTimeReadable?: string;
 | 
			
		||||
    closeTimeReadable?: string;
 | 
			
		||||
 | 
			
		||||
    protected userId?: number;
 | 
			
		||||
    protected syncEventName = AddonModChoiceSyncProvider.AUTO_SYNCED;
 | 
			
		||||
    protected hasAnsweredOnline = false;
 | 
			
		||||
    protected now = Date.now();
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected content?: IonContent,
 | 
			
		||||
        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
        super('AddonModChoiceIndexComponent', content, courseContentsPage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        this.userId = CoreSites.getCurrentSiteUserId();
 | 
			
		||||
 | 
			
		||||
        await this.loadContent(false, true);
 | 
			
		||||
 | 
			
		||||
        if (!this.choice) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModChoice.logView(this.choice.id, this.choice.name);
 | 
			
		||||
 | 
			
		||||
            await CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateContent(): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(AddonModChoice.invalidateChoiceData(this.courseId));
 | 
			
		||||
 | 
			
		||||
        if (this.choice) {
 | 
			
		||||
            promises.push(AddonModChoice.invalidateOptions(this.choice.id));
 | 
			
		||||
            promises.push(AddonModChoice.invalidateResults(this.choice.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected isRefreshSyncNeeded(syncEventData: AddonModChoiceAutoSyncData): boolean {
 | 
			
		||||
        if (this.choice && syncEventData.choiceId == this.choice.id && syncEventData.userId == this.userId) {
 | 
			
		||||
            this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        this.now = Date.now();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the choice.
 | 
			
		||||
                const updated = await this.syncActivity(showErrors);
 | 
			
		||||
 | 
			
		||||
                if (updated) {
 | 
			
		||||
                    // Responses were sent, update the choice.
 | 
			
		||||
                    this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.choice.timeopen = (this.choice.timeopen || 0) * 1000;
 | 
			
		||||
            this.choice.timeclose = (this.choice.timeclose || 0) * 1000;
 | 
			
		||||
            this.openTimeReadable = CoreTimeUtils.userDate(this.choice.timeopen);
 | 
			
		||||
            this.closeTimeReadable = CoreTimeUtils.userDate(this.choice.timeclose);
 | 
			
		||||
 | 
			
		||||
            this.description = this.choice.intro;
 | 
			
		||||
            this.choiceNotOpenYet = !!this.choice.timeopen && this.choice.timeopen > this.now;
 | 
			
		||||
            this.choiceClosed = !!this.choice.timeclose && this.choice.timeclose <= this.now;
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.choice);
 | 
			
		||||
 | 
			
		||||
            // Check if there are responses stored in offline.
 | 
			
		||||
            this.hasOffline = await AddonModChoiceOffline.hasResponse(this.choice.id);
 | 
			
		||||
 | 
			
		||||
            // We need fetchOptions to finish before calling fetchResults because it needs hasAnsweredOnline variable.
 | 
			
		||||
            await this.fetchOptions(this.choice);
 | 
			
		||||
 | 
			
		||||
            await this.fetchResults(this.choice);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to get choice options.
 | 
			
		||||
     *
 | 
			
		||||
     * @param choice Choice data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchOptions(choice: AddonModChoiceChoice): Promise<void> {
 | 
			
		||||
        let options = await AddonModChoice.getOptions(choice.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        // Check if the user has answered (synced) to allow show results.
 | 
			
		||||
        this.hasAnsweredOnline = options.some((option) => option.checked);
 | 
			
		||||
 | 
			
		||||
        if (this.hasOffline) {
 | 
			
		||||
            options = await this.getOfflineResponses(choice, options);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const isOpen = this.isChoiceOpen(choice);
 | 
			
		||||
 | 
			
		||||
        this.selectedOption = { id: -1 }; // Single choice model.
 | 
			
		||||
        const hasAnswered = options.some((option) => {
 | 
			
		||||
            if (!option.checked) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!choice.allowmultiple) {
 | 
			
		||||
                this.selectedOption.id = option.id;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.canEdit = isOpen && (choice.allowupdate! || !hasAnswered);
 | 
			
		||||
        this.canDelete = isOpen && choice.allowupdate! && hasAnswered;
 | 
			
		||||
        this.options = options;
 | 
			
		||||
 | 
			
		||||
        if (!this.canEdit) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Calculate the publish info message.
 | 
			
		||||
        switch (choice.showresults) {
 | 
			
		||||
            case AddonModChoiceProvider.RESULTS_NOT:
 | 
			
		||||
                this.publishInfo = 'addon.mod_choice.publishinfonever';
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case AddonModChoiceProvider.RESULTS_AFTER_ANSWER:
 | 
			
		||||
                if (choice.publish == AddonModChoiceProvider.PUBLISH_ANONYMOUS) {
 | 
			
		||||
                    this.publishInfo = 'addon.mod_choice.publishinfoanonafter';
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.publishInfo = 'addon.mod_choice.publishinfofullafter';
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case AddonModChoiceProvider.RESULTS_AFTER_CLOSE:
 | 
			
		||||
                if (choice.publish == AddonModChoiceProvider.PUBLISH_ANONYMOUS) {
 | 
			
		||||
                    this.publishInfo = 'addon.mod_choice.publishinfoanonclose';
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.publishInfo = 'addon.mod_choice.publishinfofullclose';
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                // No need to inform the user since it's obvious that the results are being published.
 | 
			
		||||
                this.publishInfo = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get offline responses.
 | 
			
		||||
     *
 | 
			
		||||
     * @param choice Choice.
 | 
			
		||||
     * @param options Online options.
 | 
			
		||||
     * @return Promise resolved with the options.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getOfflineResponses(
 | 
			
		||||
        choice: AddonModChoiceChoice,
 | 
			
		||||
        options: AddonModChoiceOption[],
 | 
			
		||||
    ): Promise<AddonModChoiceOption[]> {
 | 
			
		||||
        const response = await AddonModChoiceOffline.getResponse(choice.id);
 | 
			
		||||
 | 
			
		||||
        const optionsMap: {[id: number]: AddonModChoiceOption} = {};
 | 
			
		||||
        options.forEach((option) => {
 | 
			
		||||
            optionsMap[option.id] = option;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Update options with the offline data.
 | 
			
		||||
        if (response.deleting) {
 | 
			
		||||
            // Uncheck selected options.
 | 
			
		||||
            if (response.responses.length > 0) {
 | 
			
		||||
                // Uncheck all options selected in responses.
 | 
			
		||||
                response.responses.forEach((selected) => {
 | 
			
		||||
                    if (optionsMap[selected] && optionsMap[selected].checked) {
 | 
			
		||||
                        optionsMap[selected].checked = false;
 | 
			
		||||
                        optionsMap[selected].countanswers--;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                // On empty responses, uncheck all selected.
 | 
			
		||||
                Object.keys(optionsMap).forEach((key) => {
 | 
			
		||||
                    if (optionsMap[key].checked) {
 | 
			
		||||
                        optionsMap[key].checked = false;
 | 
			
		||||
                        optionsMap[key].countanswers--;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Uncheck all options to check again the offlines'.
 | 
			
		||||
            Object.keys(optionsMap).forEach((key) => {
 | 
			
		||||
                if (optionsMap[key].checked) {
 | 
			
		||||
                    optionsMap[key].checked = false;
 | 
			
		||||
                    optionsMap[key].countanswers--;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            // Then check selected ones.
 | 
			
		||||
            response.responses.forEach((selected) => {
 | 
			
		||||
                if (optionsMap[selected]) {
 | 
			
		||||
                    optionsMap[selected].checked = true;
 | 
			
		||||
                    optionsMap[selected].countanswers++;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Convert it again to array.
 | 
			
		||||
        return Object.keys(optionsMap).map((key) => optionsMap[key]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to get choice results.
 | 
			
		||||
     *
 | 
			
		||||
     * @param choice Choice.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchResults(choice: AddonModChoiceChoice): Promise<void> {
 | 
			
		||||
        if (this.choiceNotOpenYet) {
 | 
			
		||||
            // Cannot see results yet.
 | 
			
		||||
            this.canSeeResults = false;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const results = await AddonModChoice.getResults(choice.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        let hasVotes = false;
 | 
			
		||||
        this.data = [];
 | 
			
		||||
        this.labels = [];
 | 
			
		||||
 | 
			
		||||
        this.results = results.map((result: AddonModChoiceResultFormatted) => {
 | 
			
		||||
            if (result.numberofuser > 0) {
 | 
			
		||||
                hasVotes = true;
 | 
			
		||||
            }
 | 
			
		||||
            this.data.push(result.numberofuser);
 | 
			
		||||
            this.labels.push(result.text);
 | 
			
		||||
 | 
			
		||||
            return Object.assign(result, { percentageamountfixed: result.percentageamount.toFixed(1) });
 | 
			
		||||
        });
 | 
			
		||||
        this.canSeeResults = hasVotes || AddonModChoice.canStudentSeeResults(choice, this.hasAnsweredOnline);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a choice is open.
 | 
			
		||||
     *
 | 
			
		||||
     * @param choice Choice data.
 | 
			
		||||
     * @return True if choice is open, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    protected isChoiceOpen(choice: AddonModChoiceChoice): boolean {
 | 
			
		||||
        return (!choice.timeopen || choice.timeopen <= this.now) && (!choice.timeclose || choice.timeclose > this.now);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return true if the user has selected at least one option.
 | 
			
		||||
     *
 | 
			
		||||
     * @return True if the user has responded.
 | 
			
		||||
     */
 | 
			
		||||
    canSave(): boolean {
 | 
			
		||||
        if (!this.choice) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.choice.allowmultiple) {
 | 
			
		||||
            return this.options.some((option) => option.checked);
 | 
			
		||||
        } else {
 | 
			
		||||
            return this.selectedOption.id !== -1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save options selected.
 | 
			
		||||
     */
 | 
			
		||||
    async save(): Promise<void> {
 | 
			
		||||
        const choice = this.choice!;
 | 
			
		||||
 | 
			
		||||
        // Only show confirm if choice doesn't allow update.
 | 
			
		||||
        if (!choice.allowupdate) {
 | 
			
		||||
            await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const responses: number[] = [];
 | 
			
		||||
        if (choice.allowmultiple) {
 | 
			
		||||
            this.options.forEach((option) => {
 | 
			
		||||
                if (option.checked) {
 | 
			
		||||
                    responses.push(option.id);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            responses.push(this.selectedOption.id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading('core.sending', true);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const online = await AddonModChoice.submitResponse(choice.id, choice.name, this.courseId, responses);
 | 
			
		||||
 | 
			
		||||
            this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
            if (online) {
 | 
			
		||||
                CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: this.moduleName });
 | 
			
		||||
                // Check completion since it could be configured to complete once the user answers the choice.
 | 
			
		||||
                CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await this.dataUpdated(online);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_choice.cannotsubmit', true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete options selected.
 | 
			
		||||
     */
 | 
			
		||||
    async delete(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreDomUtils.showDeleteConfirm();
 | 
			
		||||
        } catch {
 | 
			
		||||
            // User cancelled.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading('core.sending', true);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModChoice.deleteResponses(this.choice!.id, this.choice!.name, this.courseId);
 | 
			
		||||
 | 
			
		||||
            this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
            // Refresh the data. Don't call dataUpdated because deleting an answer doesn't mark the choice as outdated.
 | 
			
		||||
            await this.refreshContent(false);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_choice.cannotsubmit', true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function to call when some data has changed. It will refresh/prefetch data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param online Whether the data was sent to server or stored in offline.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async dataUpdated(online: boolean): Promise<void> {
 | 
			
		||||
        if (!online || !this.isPrefetched) {
 | 
			
		||||
            // Not downloaded, just refresh the data.
 | 
			
		||||
            return this.refreshContent(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // The choice is downloaded, update the data.
 | 
			
		||||
            await AddonModChoiceSync.prefetchAfterUpdate(AddonModChoicePrefetchHandler.instance, this.module, this.courseId);
 | 
			
		||||
 | 
			
		||||
            // Update the view.
 | 
			
		||||
            this.showLoadingAndFetch(false, false);
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Prefetch failed, refresh the data.
 | 
			
		||||
            return this.refreshContent(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the sync of the activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected sync(): Promise<AddonModChoiceSyncResult> {
 | 
			
		||||
        return AddonModChoiceSync.syncChoice(this.choice!.id, this.userId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sync has succeed from result sync data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param result Data returned on the sync function.
 | 
			
		||||
     * @return Whether it succeed or not.
 | 
			
		||||
     */
 | 
			
		||||
    protected hasSyncSucceed(result: AddonModChoiceSyncResult): boolean {
 | 
			
		||||
        return result.updated;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Choice result with some calculated data.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModChoiceResultFormatted = AddonModChoiceResult & {
 | 
			
		||||
    percentageamountfixed: string; // Percentage of users answers with fixed decimals.
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										28
									
								
								src/addons/mod/choice/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/addons/mod/choice/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
{
 | 
			
		||||
    "cannotsubmit": "Sorry, there was a problem submitting your choice. Please try again.",
 | 
			
		||||
    "choiceoptions": "Choice options",
 | 
			
		||||
    "errorgetchoice": "Error getting choice data.",
 | 
			
		||||
    "expired": "This activity closed on {{$a}}.",
 | 
			
		||||
    "full": "(Full)",
 | 
			
		||||
    "limita": "Limit: {{$a}}",
 | 
			
		||||
    "modulenameplural": "Choices",
 | 
			
		||||
    "noresultsviewable": "The results are not currently viewable.",
 | 
			
		||||
    "notopenyet": "This activity is not available until {{$a}}.",
 | 
			
		||||
    "numberofuser": "Number of responses",
 | 
			
		||||
    "numberofuserinpercentage": "Percentage of responses",
 | 
			
		||||
    "previewonly": "This is just a preview of the available options for this activity. You will not be able to submit your choice until {{$a}}.",
 | 
			
		||||
    "publishinfoanonafter": "Anonymous results will be published after you answer.",
 | 
			
		||||
    "publishinfoanonclose": "Anonymous results will be published after the activity is closed.",
 | 
			
		||||
    "publishinfofullafter": "Full results, showing everyone's choices, will be published after you answer.",
 | 
			
		||||
    "publishinfofullclose": "Full results, showing everyone's choices, will be published after the activity is closed.",
 | 
			
		||||
    "publishinfonever": "The results of this activity will not be published after you answer.",
 | 
			
		||||
    "removemychoice": "Remove my choice",
 | 
			
		||||
    "responses": "Responses",
 | 
			
		||||
    "responsesa": "Responses: {{$a}}",
 | 
			
		||||
    "responsesresultgraphdescription": "{{number}}% of the users chose the option: {{text}}.",
 | 
			
		||||
    "responsesresultgraphheader": "Graph display",
 | 
			
		||||
    "resultsnotsynced": "Your last response must be synchronised before it is included in the results.",
 | 
			
		||||
    "savemychoice": "Save my choice",
 | 
			
		||||
    "userchoosethisoption": "Users who chose this option",
 | 
			
		||||
    "yourselection": "Your selection"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/addons/mod/choice/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/addons/mod/choice/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <!-- The buttons defined by the component will be added in here. -->
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <addon-mod-choice-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"></addon-mod-choice-index>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										30
									
								
								src/addons/mod/choice/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/mod/choice/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// 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 { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
			
		||||
import { AddonModChoiceIndexComponent } from '../../components/index/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a choice.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-choice-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModChoiceIndexPage extends CoreCourseModuleMainActivityPage<AddonModChoiceIndexComponent> {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(AddonModChoiceIndexComponent) activityComponent?: AddonModChoiceIndexComponent;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user