MOBILE-2351 survey: Implement the survey module
parent
253e14cd57
commit
978f69ea50
|
@ -45,7 +45,6 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
||||||
protected translate: TranslateService, private prefetchHandler: AddonModResourcePrefetchHandler,
|
protected translate: TranslateService, private prefetchHandler: AddonModResourcePrefetchHandler,
|
||||||
private resourceHelper: AddonModResourceHelperProvider) {
|
private resourceHelper: AddonModResourceHelperProvider) {
|
||||||
super(textUtils, courseHelper, translate, domUtils);
|
super(textUtils, courseHelper, translate, domUtils);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
import { AddonModSurveyIndexComponent } from './index/index';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonModSurveyIndexComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonModSurveyIndexComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonModSurveyIndexComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonModSurveyComponentsModule {}
|
|
@ -0,0 +1,105 @@
|
||||||
|
<!-- Buttons to add to the header. -->
|
||||||
|
<core-navbar-buttons end>
|
||||||
|
<core-context-menu>
|
||||||
|
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></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" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="size" [priority]="400" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item>
|
||||||
|
</core-context-menu>
|
||||||
|
</core-navbar-buttons>
|
||||||
|
|
||||||
|
<!-- Content. -->
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
|
||||||
|
<core-course-module-description *ngIf="mode != 'iframe'" [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
|
||||||
|
|
||||||
|
<!-- Survey already done -->
|
||||||
|
<ion-card padding *ngIf="survey && survey.surveydone">
|
||||||
|
<p padding>{{ 'addon.mod_survey.surveycompletednograph' | translate }}</p>
|
||||||
|
<a ion-button block icon-start [href]="externalUrl" core-link>
|
||||||
|
<ion-icon name="open" start></ion-icon>
|
||||||
|
{{ 'addon.mod_survey.results' | translate }}
|
||||||
|
</a>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<!-- Survey done in offline but not synchronized -->
|
||||||
|
<div class="core-warning-card" icon-start *ngIf="hasOffline">
|
||||||
|
<ion-icon name="warning"></ion-icon>
|
||||||
|
{{ 'core.hasdatatosync' | translate: {$a: moduleName} }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Survey questions -->
|
||||||
|
<section *ngIf="survey && !survey.surveydone && !hasOffline && questions && questions.length">
|
||||||
|
|
||||||
|
<ng-container *ngFor="let question of questions; let index=index; let isEven=even;">
|
||||||
|
<!-- Parent question (Category header) -->
|
||||||
|
<div *ngIf="question.multi && question.multi.length" [attr.padding-top]="index == 1">
|
||||||
|
<h3 padding-horizontal>{{ question.text }}</h3>
|
||||||
|
<ion-grid no-padding>
|
||||||
|
<ion-row no-padding align-items-center class="hidden-phone">
|
||||||
|
<ion-col col-7>
|
||||||
|
<div padding>{{ 'addon.mod_survey.responses' | translate }}</div>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-center *ngFor="let option of question.options">
|
||||||
|
<div padding>{{ option }}</div>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
<ion-item text-wrap [class.even]="isEven" class="addon-mod_survey-question">
|
||||||
|
<p>{{ question.intro }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subquestion -->
|
||||||
|
<ion-grid no-padding *ngIf="question.parent !== 0" text-wrap [class.even]="isEven">
|
||||||
|
<ion-row no-padding nowrap align-items-center radio-group [(ngModel)]="answers[question.name]" [required]="question.required">
|
||||||
|
<ion-col col-7>
|
||||||
|
<ion-label padding-horizontal [core-mark-required]="question.required" id="addon-mod_survey-{{question.name}}"><strong>{{question.num}}.</strong> {{ question.text }}</ion-label>
|
||||||
|
</ion-col>
|
||||||
|
|
||||||
|
<!-- Tablet view: radio buttons -->
|
||||||
|
<ion-col class="hidden-phone" text-center *ngFor="let option of question.options; let value=index;">
|
||||||
|
<ion-radio [value]="value + 1" [attr.aria-labelledby]="'addon-mod_survey-'+question.name"></ion-radio>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col class="hidden-tablet">
|
||||||
|
<ion-select padding [(ngModel)]="answers[question.name]" [attr.aria-labelledby]="'addon-mod_survey-'+question.name" interface="popover" [required]="question.required">
|
||||||
|
<ion-option value="-1" selected disabled>{{ 'core.choose' | translate }}</ion-option>
|
||||||
|
<ion-option *ngFor="let option of question.options; let value=index;" [value]="value +1">{{option}}</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<!-- Single question (don't belong to a category) -->
|
||||||
|
<ng-container *ngIf="(!question.multi || question.multi.length == 0) && question.parent === 0">
|
||||||
|
<ion-grid no-padding text-wrap *ngIf="question.type > 0" [class.even]="isEven">
|
||||||
|
<ion-row no-padding align-items-center>
|
||||||
|
<ion-col col-7>
|
||||||
|
<ion-label [core-mark-required]="question.required" padding-horizontal id="addon-mod_survey-{{question.name}}"><strong>{{question.num}}.</strong> {{ question.text }}</ion-label>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col col-5>
|
||||||
|
<ion-select padding [(ngModel)]="answers[question.name]" [attr.aria-labelledby]="'addon-mod_survey-'+question.name" interface="popover" [required]="question.required">
|
||||||
|
<ion-option *ngFor="let option of question.options; let value=index;" [value]="value">{{option}}</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<ion-item *ngIf="question.type === 0" text-wrap [class.even]="isEven">
|
||||||
|
<ion-label stacked [core-mark-required]="question.required" id="addon-mod_survey-{{question.name}}">
|
||||||
|
<strong>{{question.num}}.</strong> {{ question.text }}
|
||||||
|
</ion-label>
|
||||||
|
<ion-textarea [(ngModel)]="answers[question.name]" class="addon-mod_survey-textarea" [attr.aria-label]="question.text" [required]="question.required"></ion-textarea>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<button ion-button block (click)="submit()" [disabled]="!isValidResponse()">{{ 'core.submit' | translate }}</button>
|
||||||
|
</ion-item>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,33 @@
|
||||||
|
addon-mod-survey-index {
|
||||||
|
|
||||||
|
.label, .label[stacked] {
|
||||||
|
font-size: initial;
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-mod_survey-question {
|
||||||
|
border-top: 1px solid $gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-grid {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-select {
|
||||||
|
float: right;
|
||||||
|
max-width: none;
|
||||||
|
.select-text {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.even {
|
||||||
|
background-color: $gray-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-mod_survey-textarea textarea {
|
||||||
|
height: 100px;
|
||||||
|
border: 1px solid $gray-dark;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
// (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 { Content } from 'ionic-angular';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Network } from '@ionic-native/network';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
|
||||||
|
import { AddonModSurveyProvider } from '../../providers/survey';
|
||||||
|
import { AddonModSurveyHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonModSurveyOfflineProvider } from '../../providers/offline';
|
||||||
|
import { AddonModSurveySyncProvider } from '../../providers/sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays a survey.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-mod-survey-index',
|
||||||
|
templateUrl: 'index.html',
|
||||||
|
})
|
||||||
|
export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent {
|
||||||
|
component = AddonModSurveyProvider.COMPONENT;
|
||||||
|
moduleName = 'survey';
|
||||||
|
|
||||||
|
survey: any;
|
||||||
|
questions: any;
|
||||||
|
answers = {};
|
||||||
|
|
||||||
|
protected userId: number;
|
||||||
|
protected syncEventName = AddonModSurveySyncProvider.AUTO_SYNCED;
|
||||||
|
|
||||||
|
constructor(private surveyProvider: AddonModSurveyProvider, protected courseProvider: CoreCourseProvider,
|
||||||
|
protected domUtils: CoreDomUtilsProvider, protected appProvider: CoreAppProvider,
|
||||||
|
protected courseHelper: CoreCourseHelperProvider, protected translate: TranslateService, network: Network,
|
||||||
|
private surveyHelper: AddonModSurveyHelperProvider, protected sitesProvider: CoreSitesProvider,
|
||||||
|
protected eventsProvider: CoreEventsProvider, private surveyOffline: AddonModSurveyOfflineProvider,
|
||||||
|
private surveySync: AddonModSurveySyncProvider, @Optional() private content: Content,
|
||||||
|
protected textUtils: CoreTextUtilsProvider) {
|
||||||
|
super(textUtils, courseHelper, translate, domUtils, sitesProvider, courseProvider, network, appProvider, eventsProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
this.userId = this.sitesProvider.getCurrentSiteUserId();
|
||||||
|
|
||||||
|
this.loadContent(false, true).then(() => {
|
||||||
|
this.surveyProvider.logView(this.survey.id).then(() => {
|
||||||
|
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.surveyProvider.invalidateSurveyData(this.courseId));
|
||||||
|
if (this.survey) {
|
||||||
|
promises.push(this.surveyProvider.invalidateQuestions(this.survey.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares sync event data with current data to check if refresh content is needed.
|
||||||
|
*
|
||||||
|
* @param {any} syncEventData Data receiven on sync observer.
|
||||||
|
* @return {boolean} True if refresh is needed, false otherwise.
|
||||||
|
*/
|
||||||
|
protected isRefreshSyncNeeded(syncEventData: any): boolean {
|
||||||
|
if (this.survey && syncEventData.surveyId == this.survey.id && syncEventData.userId == this.userId) {
|
||||||
|
this.content.scrollToTop();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download survey contents.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
|
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
||||||
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
|
||||||
|
return this.surveyProvider.getSurvey(this.courseId, this.module.id).then((survey) => {
|
||||||
|
this.survey = survey;
|
||||||
|
|
||||||
|
this.description = survey.intro || survey.description;
|
||||||
|
this.dataRetrieved.emit(survey);
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
// Try to synchronize the survey.
|
||||||
|
return this.syncActivity(showErrors).then((answersSent) => {
|
||||||
|
if (answersSent) {
|
||||||
|
// Answers were sent, update the survey.
|
||||||
|
return this.surveyProvider.getSurvey(this.courseId, this.module.id).then((survey) => {
|
||||||
|
this.survey = survey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Check if there are answers stored in offline.
|
||||||
|
return this.surveyOffline.hasAnswers(this.survey.id);
|
||||||
|
}).then((hasOffline) => {
|
||||||
|
this.hasOffline = this.survey.surveydone ? false : hasOffline;
|
||||||
|
|
||||||
|
if (!this.survey.surveydone && !this.hasOffline) {
|
||||||
|
return this.fetchQuestions();
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// All data obtained, now fill the context menu.
|
||||||
|
this.fillContextMenu(refresh);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to get survey questions.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchQuestions(): Promise<any> {
|
||||||
|
return this.surveyProvider.getQuestions(this.survey.id).then((questions) => {
|
||||||
|
this.questions = this.surveyHelper.formatQuestions(questions);
|
||||||
|
|
||||||
|
// Init answers object.
|
||||||
|
this.questions.forEach((q) => {
|
||||||
|
if (q.name) {
|
||||||
|
const isTextArea = q.multi && q.multi.length === 0 && q.type === 0;
|
||||||
|
this.answers[q.name] = q.required ? -1 : (isTextArea ? '' : '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.multi && !q.multi.length && q.parent === 0 && q.type > 0) {
|
||||||
|
// Options shown in a select. Remove all HTML.
|
||||||
|
q.options = q.options.map((option) => {
|
||||||
|
return this.textUtils.cleanTags(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if answers are valid to be submitted.
|
||||||
|
*
|
||||||
|
* @return {boolean} If answers are valid
|
||||||
|
*/
|
||||||
|
isValidResponse(): boolean {
|
||||||
|
for (const x in this.answers) {
|
||||||
|
if (this.answers[x] === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save options selected.
|
||||||
|
*/
|
||||||
|
submit(): void {
|
||||||
|
this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
|
||||||
|
const answers = [],
|
||||||
|
modal = this.domUtils.showModalLoading('core.sending', true);
|
||||||
|
|
||||||
|
for (const x in this.answers) {
|
||||||
|
answers.push({
|
||||||
|
key: x,
|
||||||
|
value: this.answers[x]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.surveyProvider.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers).then(() => {
|
||||||
|
this.content.scrollToTop();
|
||||||
|
|
||||||
|
return this.refreshContent(false);
|
||||||
|
}).catch((message) => {
|
||||||
|
this.domUtils.showErrorModalDefault(message, 'addon.mod_survey.cannotsubmitsurvey', true);
|
||||||
|
}).finally(() => {
|
||||||
|
modal.dismiss();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the sync of the activity.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected sync(): Promise<any> {
|
||||||
|
return this.surveySync.syncSurvey(this.survey.id, this.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if sync has succeed from result sync data.
|
||||||
|
*
|
||||||
|
* @param {any} result Data returned on the sync function.
|
||||||
|
* @return {boolean} If suceed or not.
|
||||||
|
*/
|
||||||
|
protected hasSyncSucceed(result: any): boolean {
|
||||||
|
return result.answersSent;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"cannotsubmitsurvey": "Sorry, there was a problem submitting your survey. Please try again.",
|
||||||
|
"errorgetsurvey": "Error getting survey data.",
|
||||||
|
"ifoundthat": "I found that",
|
||||||
|
"ipreferthat": "I prefer that",
|
||||||
|
"responses": "Responses",
|
||||||
|
"results": "Results",
|
||||||
|
"surveycompletednograph": "You have completed this survey."
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||||
|
|
||||||
|
<ion-buttons end>
|
||||||
|
<!-- The buttons defined by the component will be added in here. -->
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="surveyComponent.loaded" (ionRefresh)="surveyComponent.doRefresh($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<addon-mod-survey-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"></addon-mod-survey-index>
|
||||||
|
</ion-content>
|
|
@ -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 { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { AddonModSurveyComponentsModule } from '../../components/components.module';
|
||||||
|
import { AddonModSurveyIndexPage } from './index';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonModSurveyIndexPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreDirectivesModule,
|
||||||
|
AddonModSurveyComponentsModule,
|
||||||
|
IonicPageModule.forChild(AddonModSurveyIndexPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModSurveyIndexPageModule {}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// (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 { AddonModSurveyIndexComponent } from '../../components/index/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays a survey.
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'addon-mod-survey-index' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-mod-survey-index',
|
||||||
|
templateUrl: 'index.html',
|
||||||
|
})
|
||||||
|
export class AddonModSurveyIndexPage {
|
||||||
|
@ViewChild(AddonModSurveyIndexComponent) surveyComponent: AddonModSurveyIndexComponent;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
module: any;
|
||||||
|
courseId: number;
|
||||||
|
|
||||||
|
constructor(navParams: NavParams) {
|
||||||
|
this.module = navParams.get('module') || {};
|
||||||
|
this.courseId = navParams.get('courseId');
|
||||||
|
this.title = this.module.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update some data based on the survey instance.
|
||||||
|
*
|
||||||
|
* @param {any} survey Survey instance.
|
||||||
|
*/
|
||||||
|
updateData(survey: any): void {
|
||||||
|
this.title = survey.name || this.title;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
// (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 { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides helper functions for surveys.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveyHelperProvider {
|
||||||
|
|
||||||
|
constructor(private translate: TranslateService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a string with values separated by commas into an array.
|
||||||
|
*
|
||||||
|
* @param {string} value Value to convert.
|
||||||
|
* @return {string[]} Array.
|
||||||
|
*/
|
||||||
|
protected commaStringToArray(value: string): string[] {
|
||||||
|
if (typeof value == 'string') {
|
||||||
|
if (value.length > 0) {
|
||||||
|
return value.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the parent questions and puts them in an object: ID -> question.
|
||||||
|
*
|
||||||
|
* @param {Object[]} questions Questions.
|
||||||
|
* @return {any} Object with parent questions.
|
||||||
|
*/
|
||||||
|
protected getParentQuestions(questions: any[]): any {
|
||||||
|
const parents = {};
|
||||||
|
|
||||||
|
questions.forEach((question) => {
|
||||||
|
if (question.parent === 0) {
|
||||||
|
parents[question.id] = question;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a questions list, turning "multi" and "options" strings into arrays and adding the properties
|
||||||
|
* 'num' and 'name'.
|
||||||
|
*
|
||||||
|
* @param {any[]} questions Questions.
|
||||||
|
* @return {any[]} Promise resolved with the formatted questions.
|
||||||
|
*/
|
||||||
|
formatQuestions(questions: any[]): any[] {
|
||||||
|
|
||||||
|
const strIPreferThat = this.translate.instant('addon.mod_survey.ipreferthat'),
|
||||||
|
strIFoundThat = this.translate.instant('addon.mod_survey.ifoundthat'),
|
||||||
|
strChoose = this.translate.instant('core.choose'),
|
||||||
|
formatted = [],
|
||||||
|
parents = this.getParentQuestions(questions);
|
||||||
|
|
||||||
|
let num = 1;
|
||||||
|
|
||||||
|
questions.forEach((question) => {
|
||||||
|
// Copy the object to prevent modifying the original.
|
||||||
|
const q1 = Object.assign({}, question),
|
||||||
|
parent = parents[q1.parent];
|
||||||
|
|
||||||
|
// Turn multi and options into arrays.
|
||||||
|
q1.multi = this.commaStringToArray(q1.multi);
|
||||||
|
q1.options = this.commaStringToArray(q1.options);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
// It's a sub-question.
|
||||||
|
q1.required = true;
|
||||||
|
|
||||||
|
if (parent.type === 1 || parent.type === 2) {
|
||||||
|
// One answer question. Set its name and add it to the returned array.
|
||||||
|
q1.name = 'q' + (parent.type == 2 ? 'P' : '') + q1.id;
|
||||||
|
q1.num = num++;
|
||||||
|
} else {
|
||||||
|
// Two answers per question (COLLES P&A). We'll add two questions.
|
||||||
|
const q2 = Object.assign({}, q1);
|
||||||
|
|
||||||
|
q1.text = strIPreferThat + ' ' + q1.text;
|
||||||
|
q1.name = 'qP' + q1.id;
|
||||||
|
q1.num = num++;
|
||||||
|
formatted.push(q1);
|
||||||
|
|
||||||
|
q2.text = strIFoundThat + ' ' + q2.text;
|
||||||
|
q2.name = 'q' + q1.id;
|
||||||
|
q2.num = num++;
|
||||||
|
formatted.push(q2);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (q1.multi && q1.multi.length === 0) {
|
||||||
|
// It's a single question.
|
||||||
|
q1.name = 'q' + q1.id;
|
||||||
|
q1.num = num++;
|
||||||
|
if (q1.type > 0) { // Add "choose" option since this question is not required.
|
||||||
|
q1.options.unshift(strChoose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted.push(q1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// (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 { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { AddonModSurveyProvider } from './survey';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to survey.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveyLinkHandler extends CoreContentLinksModuleIndexHandler {
|
||||||
|
name = 'AddonModSurveyLinkHandler';
|
||||||
|
|
||||||
|
constructor(courseHelper: CoreCourseHelperProvider) {
|
||||||
|
super(courseHelper, AddonModSurveyProvider.COMPONENT, 'survey');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// (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, NavOptions } from 'ionic-angular';
|
||||||
|
import { AddonModSurveyIndexComponent } from '../components/index/index';
|
||||||
|
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support survey modules.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveyModuleHandler implements CoreCourseModuleHandler {
|
||||||
|
name = 'survey';
|
||||||
|
|
||||||
|
constructor(private courseProvider: CoreCourseProvider) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data required to display the module in the course contents view.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object.
|
||||||
|
* @param {number} courseId The course ID.
|
||||||
|
* @param {number} sectionId The section ID.
|
||||||
|
* @return {CoreCourseModuleHandlerData} Data to render the module.
|
||||||
|
*/
|
||||||
|
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
|
||||||
|
return {
|
||||||
|
icon: this.courseProvider.getModuleIconSrc('survey'),
|
||||||
|
title: module.name,
|
||||||
|
class: 'addon-mod_survey-handler',
|
||||||
|
action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
|
||||||
|
navCtrl.push('AddonModSurveyIndexPage', {module: module, courseId: courseId}, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to render the module. This is needed to support singleactivity course format.
|
||||||
|
* The component returned must implement CoreCourseModuleMainComponent.
|
||||||
|
*
|
||||||
|
* @param {any} course The course object.
|
||||||
|
* @param {any} module The module object.
|
||||||
|
* @return {any} The component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getMainComponent(course: any, module: any): any {
|
||||||
|
return AddonModSurveyIndexComponent;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
// (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 { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle Offline survey.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveyOfflineProvider {
|
||||||
|
|
||||||
|
protected logger;
|
||||||
|
|
||||||
|
// Variables for database.
|
||||||
|
protected SURVEY_TABLE = 'mma_mod_survey_answers';
|
||||||
|
protected tablesSchema = [
|
||||||
|
{
|
||||||
|
name: this.SURVEY_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'surveyid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courseid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'answers',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timecreated',
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryKeys: ['surveyid', 'userid']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider) {
|
||||||
|
this.logger = logger.getInstance('AddonModSurveyOfflineProvider');
|
||||||
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a survey answers.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {number} [userId] User the answers belong to. If not defined, current user in site.
|
||||||
|
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
|
||||||
|
*/
|
||||||
|
deleteSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
return site.getDb().deleteRecords(this.SURVEY_TABLE, {surveyid: surveyId, userid: userId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the stored data from all the surveys.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with answers.
|
||||||
|
*/
|
||||||
|
getAllData(siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getAllRecords(this.SURVEY_TABLE).then((entries) => {
|
||||||
|
return entries.map((entry) => {
|
||||||
|
entry.answers = this.textUtils.parseJSON(entry.answers);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a survey stored answers.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {number} [userId] User the answers belong to. If not defined, current user in site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the answers.
|
||||||
|
*/
|
||||||
|
getSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.getSurveyData(surveyId, siteId, userId).then((entry) => {
|
||||||
|
return entry.answers || [];
|
||||||
|
}).catch(() => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a survey stored data.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {number} [userId] User the answers belong to. If not defined, current user in site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getSurveyData(surveyId: number, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
return site.getDb().getRecord(this.SURVEY_TABLE, {surveyid: surveyId, userid: userId}).then((entry) => {
|
||||||
|
entry.answers = this.textUtils.parseJSON(entry.answers);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are offline answers to send.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {number} [userId] User the answers belong to. If not defined, current user in site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline answers, false otherwise.
|
||||||
|
*/
|
||||||
|
hasAnswers(surveyId: number, siteId?: string, userId?: number): Promise<boolean> {
|
||||||
|
return this.getSurveyAnswers(surveyId, siteId, userId).then((answers) => {
|
||||||
|
return !!answers.length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save answers to be sent later.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} name Survey name.
|
||||||
|
* @param {number} courseId Course ID the survey belongs to.
|
||||||
|
* @param {any[]} answers Answers.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {number} [userId] User the answers belong to. If not defined, current user in site.
|
||||||
|
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||||
|
*/
|
||||||
|
saveAnswers(surveyId: number, name: string, courseId: number, answers: any[], siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
surveyid: surveyId,
|
||||||
|
name: name,
|
||||||
|
courseid: courseId,
|
||||||
|
userid: userId,
|
||||||
|
answers: JSON.stringify(answers),
|
||||||
|
timecreated: new Date().getTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertOrUpdateRecord(this.SURVEY_TABLE, entry, {surveyid: surveyId, userid: userId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
// (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, Injector } from '@angular/core';
|
||||||
|
import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler';
|
||||||
|
import { AddonModSurveyProvider } from './survey';
|
||||||
|
import { AddonModSurveyHelperProvider } from './helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to prefetch surveys.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveyPrefetchHandler extends CoreCourseModulePrefetchHandlerBase {
|
||||||
|
name = 'survey';
|
||||||
|
component = AddonModSurveyProvider.COMPONENT;
|
||||||
|
updatesNames = /^configuration$|^.*files$|^answers$/;
|
||||||
|
|
||||||
|
constructor(injector: Injector, protected surveyProvider: AddonModSurveyProvider,
|
||||||
|
protected surveyHelper: AddonModSurveyHelperProvider) {
|
||||||
|
super(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download or prefetch the content.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object returned by WS.
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {boolean} [prefetch] True to prefetch, false to download right away.
|
||||||
|
* @param {string} [dirPath] Path of the directory where to store all the content files. This is to keep the files
|
||||||
|
* relative paths and make the package work in an iframe. Undefined to download the files
|
||||||
|
* in the filepool root survey.
|
||||||
|
* @return {Promise<any>} Promise resolved when all content is downloaded. Data returned is not reliable.
|
||||||
|
*/
|
||||||
|
downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(super.downloadOrPrefetch(module, courseId, prefetch));
|
||||||
|
promises.push(this.surveyProvider.getSurvey(courseId, module.id).then((survey) => {
|
||||||
|
// If survey isn't answered, prefetch the questions.
|
||||||
|
if (!survey.surveydone) {
|
||||||
|
promises.push(this.surveyProvider.getQuestions(survey.id));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns survey intro files.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object returned by WS.
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with list of intro files.
|
||||||
|
*/
|
||||||
|
getIntroFiles(module: any, courseId: number): Promise<any[]> {
|
||||||
|
return this.surveyProvider.getSurvey(courseId, module.id).catch(() => {
|
||||||
|
// Not found, return undefined so module description is used.
|
||||||
|
}).then((survey) => {
|
||||||
|
return this.getIntroFilesFromInstance(module, survey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the prefetched content.
|
||||||
|
*
|
||||||
|
* @param {number} moduleId The module ID.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateContent(moduleId: number, courseId: number): Promise<any> {
|
||||||
|
return this.surveyProvider.invalidateContent(moduleId, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate WS calls needed to determine module status.
|
||||||
|
*
|
||||||
|
* @param {any} module Module.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @return {Promise<any>} Promise resolved when invalidated.
|
||||||
|
*/
|
||||||
|
invalidateModule(module: any, courseId: number): Promise<any> {
|
||||||
|
return this.surveyProvider.invalidateSurveyData(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean|Promise<boolean>} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
|
||||||
|
*/
|
||||||
|
isEnabled(): boolean | Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,277 @@
|
||||||
|
// (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 { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||||
|
import { AddonModSurveyOfflineProvider } from './offline';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features for surveys.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveyProvider {
|
||||||
|
static COMPONENT = 'mmaModSurvey';
|
||||||
|
|
||||||
|
protected ROOT_CACHE_KEY = 'mmaModSurvey:';
|
||||||
|
protected logger;
|
||||||
|
|
||||||
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider,
|
||||||
|
private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider,
|
||||||
|
private surveyOffline: AddonModSurveyOfflineProvider) {
|
||||||
|
this.logger = logger.getInstance('AddonModSurveyProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a survey's questions.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the questions are retrieved.
|
||||||
|
*/
|
||||||
|
getQuestions(surveyId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
surveyid: surveyId
|
||||||
|
},
|
||||||
|
preSets = {
|
||||||
|
cacheKey: this.getQuestionsCacheKey(surveyId)
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('mod_survey_get_questions', params, preSets).then((response) => {
|
||||||
|
if (response.questions) {
|
||||||
|
return response.questions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for survey questions WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getQuestionsCacheKey(surveyId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'questions:' + surveyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for survey data WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getSurveyCacheKey(courseId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'survey:' + courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a survey data.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {string} key Name of the property to check.
|
||||||
|
* @param {any} value Value to search.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the survey is retrieved.
|
||||||
|
*/
|
||||||
|
protected getSurveyDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
courseids: [courseId]
|
||||||
|
},
|
||||||
|
preSets = {
|
||||||
|
cacheKey: this.getSurveyCacheKey(courseId)
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('mod_survey_get_surveys_by_courses', params, preSets).then((response) => {
|
||||||
|
if (response && response.surveys) {
|
||||||
|
const currentSurvey = response.surveys.find((survey) => {
|
||||||
|
return survey[key] == value;
|
||||||
|
});
|
||||||
|
if (currentSurvey) {
|
||||||
|
return currentSurvey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a survey by course module ID.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {number} cmId Course module ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the survey is retrieved.
|
||||||
|
*/
|
||||||
|
getSurvey(courseId: number, cmId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a survey by ID.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {number} id Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the survey is retrieved.
|
||||||
|
*/
|
||||||
|
getSurveyById(courseId: number, id: number, siteId?: string): Promise<any> {
|
||||||
|
return this.getSurveyDataByKey(courseId, 'id', id, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the prefetched content.
|
||||||
|
*
|
||||||
|
* @param {number} moduleId The module ID.
|
||||||
|
* @param {number} courseId Course ID of the module.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.getSurvey(courseId, moduleId).then((survey) => {
|
||||||
|
const ps = [];
|
||||||
|
|
||||||
|
// Do not invalidate wiki data before getting wiki info, we need it!
|
||||||
|
ps.push(this.invalidateSurveyData(courseId, siteId));
|
||||||
|
ps.push(this.invalidateQuestions(survey.id, siteId));
|
||||||
|
|
||||||
|
return Promise.all(ps);
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModSurveyProvider.COMPONENT, moduleId));
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates survey questions.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateQuestions(surveyId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKey(this.getQuestionsCacheKey(surveyId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates survey data.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateSurveyData(courseId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKey(this.getSurveyCacheKey(courseId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the survey as being viewed.
|
||||||
|
*
|
||||||
|
* @param {number} id Module ID.
|
||||||
|
* @return {Promise<any>} Promise resolved when the WS call is successful.
|
||||||
|
*/
|
||||||
|
logView(id: number): Promise<any> {
|
||||||
|
const params = {
|
||||||
|
surveyid: id
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.sitesProvider.getCurrentSite().write('mod_survey_view_survey', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send survey answers. If cannot send them to Moodle, they'll be stored in offline to be sent later.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {string} name Survey name.
|
||||||
|
* @param {number} courseId Course ID the survey belongs to.
|
||||||
|
* @param {any[]} answers Answers.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with boolean if success: true if answers were sent to server,
|
||||||
|
* false if stored in device.
|
||||||
|
*/
|
||||||
|
submitAnswers(surveyId: number, name: string, courseId: number, answers: any[], siteId?: string): Promise<boolean> {
|
||||||
|
// Convenience function to store a survey to be synchronized later.
|
||||||
|
const storeOffline = (): Promise<any> => {
|
||||||
|
return this.surveyOffline.saveAnswers(surveyId, name, courseId, answers, siteId).then(() => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (!this.appProvider.isOnline()) {
|
||||||
|
// App is offline, store the message.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's already answers to be sent to the server, discard it first.
|
||||||
|
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId).then(() => {
|
||||||
|
// Device is online, try to send them to server.
|
||||||
|
return this.submitAnswersOnline(surveyId, answers, siteId).then(() => {
|
||||||
|
return true;
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// It's a WebService error, the user cannot send the message so don't store it.
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't connect to server, store in offline.
|
||||||
|
return storeOffline();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send survey answers to Moodle.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {any[]} answers Answers.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when answers are successfully submitted. Rejected with object containing
|
||||||
|
* the error message (if any) and a boolean indicating if the error was returned by WS.
|
||||||
|
*/
|
||||||
|
submitAnswersOnline(surveyId: number, answers: any[], siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
surveyid: surveyId,
|
||||||
|
answers: answers
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.write('mod_survey_submit_answers', params).then((response) => {
|
||||||
|
if (!response.status) {
|
||||||
|
// There was an error, and it should be translated already.
|
||||||
|
return this.utils.createFakeWSError('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// (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 { CoreCronHandler } from '@providers/cron';
|
||||||
|
import { AddonModSurveySyncProvider } from './sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronization cron handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveySyncCronHandler implements CoreCronHandler {
|
||||||
|
name = 'AddonModSurveySyncCronHandler';
|
||||||
|
|
||||||
|
constructor(private surveySync: AddonModSurveySyncProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the process.
|
||||||
|
* Receives the ID of the site affected, undefined for all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||||
|
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||||
|
*/
|
||||||
|
execute(siteId?: string): Promise<any> {
|
||||||
|
return this.surveySync.syncAllSurveys(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time between consecutive executions.
|
||||||
|
*
|
||||||
|
* @return {number} Time between consecutive executions (in ms).
|
||||||
|
*/
|
||||||
|
getInterval(): number {
|
||||||
|
return 600000; // 10 minutes.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
// (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 { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { AddonModSurveyOfflineProvider } from './offline';
|
||||||
|
import { AddonModSurveyProvider } from './survey';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to sync surveys.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModSurveySyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
|
static AUTO_SYNCED = 'addon_mod_survey_autom_synced';
|
||||||
|
protected componentTranslate: string;
|
||||||
|
|
||||||
|
constructor(protected sitesProvider: CoreSitesProvider, protected loggerProvider: CoreLoggerProvider,
|
||||||
|
protected appProvider: CoreAppProvider, private surveyOffline: AddonModSurveyOfflineProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider, private surveyProvider: AddonModSurveyProvider,
|
||||||
|
private translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider,
|
||||||
|
courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider) {
|
||||||
|
super('AddonModSurveySyncProvider', sitesProvider, loggerProvider, appProvider, syncProvider, textUtils);
|
||||||
|
this.componentTranslate = courseProvider.translateModuleName('survey');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ID of a survey sync.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {number} userId User the answers belong to.
|
||||||
|
* @return {string} Sync ID.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
getSyncId (surveyId: number, userId: number): string {
|
||||||
|
return surveyId + '#' + userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to synchronize all the surveys in a certain site or in all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
syncAllSurveys(siteId?: string): Promise<any> {
|
||||||
|
return this.syncOnSites('all surveys', this.syncAllSurveysFunc.bind(this), undefined, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync all pending surveys on a site.
|
||||||
|
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||||
|
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
protected syncAllSurveysFunc(siteId?: string): Promise<any> {
|
||||||
|
// Get all survey answers pending to be sent in the site.
|
||||||
|
return this.surveyOffline.getAllData(siteId).then((entries) => {
|
||||||
|
// Sync all surveys.
|
||||||
|
const promises = entries.map((entry) => {
|
||||||
|
return this.syncSurvey(entry.surveyid, entry.userid, siteId).then((result) => {
|
||||||
|
if (result && result.answersSent) {
|
||||||
|
// Sync successful, send event.
|
||||||
|
this.eventsProvider.trigger(AddonModSurveySyncProvider.AUTO_SYNCED, {
|
||||||
|
surveyId: entry.surveyid,
|
||||||
|
userId: entry.userid,
|
||||||
|
warnings: result.warnings
|
||||||
|
}, siteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize a survey.
|
||||||
|
*
|
||||||
|
* @param {number} surveyId Survey ID.
|
||||||
|
* @param {number} userId User the answers belong to.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
syncSurvey(surveyId: number, userId: number, siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const syncId = this.getSyncId(surveyId, userId);
|
||||||
|
if (this.isSyncing(syncId, siteId)) {
|
||||||
|
// There's already a sync ongoing for this survey and user, return the promise.
|
||||||
|
return this.getOngoingSync(syncId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
let courseId;
|
||||||
|
const result = {
|
||||||
|
warnings: [],
|
||||||
|
answersSent: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.logger.debug(`Try to sync survey '${surveyId}' for user '${userId}'`);
|
||||||
|
|
||||||
|
// Get answers to be sent.
|
||||||
|
const syncPromise = this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => {
|
||||||
|
// No offline data found, return empty object.
|
||||||
|
return {};
|
||||||
|
}).then((data) => {
|
||||||
|
if (!data.answers || !data.answers.length) {
|
||||||
|
// Nothing to sync.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.appProvider.isOnline()) {
|
||||||
|
// Cannot sync in offline.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
courseId = data.courseid;
|
||||||
|
|
||||||
|
// Send the answers.
|
||||||
|
return this.surveyProvider.submitAnswersOnline(surveyId, data.answers, siteId).then(() => {
|
||||||
|
result.answersSent = true;
|
||||||
|
|
||||||
|
// Answers sent, delete them.
|
||||||
|
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId, userId);
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
|
||||||
|
// The WebService has thrown an error, this means that answers cannot be submitted. Delete them.
|
||||||
|
result.answersSent = true;
|
||||||
|
|
||||||
|
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId, userId).then(() => {
|
||||||
|
// Answers deleted, add a warning.
|
||||||
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
|
component: this.componentTranslate,
|
||||||
|
name: data.name,
|
||||||
|
error: error.error
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't connect to server, reject.
|
||||||
|
return Promise.reject(error && error.error);
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
if (courseId) {
|
||||||
|
// Data has been sent to server, update survey data.
|
||||||
|
return this.surveyProvider.invalidateSurveyData(courseId, siteId).then(() => {
|
||||||
|
return this.surveyProvider.getSurveyById(courseId, surveyId, siteId);
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Sync finished, set sync time.
|
||||||
|
return this.setSyncTime(syncId, siteId);
|
||||||
|
}).then(() => {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.addOngoingSync(syncId, syncPromise, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// (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 { CoreCronDelegate } from '@providers/cron';
|
||||||
|
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||||
|
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||||
|
import { AddonModSurveyComponentsModule } from './components/components.module';
|
||||||
|
import { AddonModSurveyModuleHandler } from './providers/module-handler';
|
||||||
|
import { AddonModSurveyProvider } from './providers/survey';
|
||||||
|
import { AddonModSurveyLinkHandler } from './providers/link-handler';
|
||||||
|
import { AddonModSurveyHelperProvider } from './providers/helper';
|
||||||
|
import { AddonModSurveyPrefetchHandler } from './providers/prefetch-handler';
|
||||||
|
import { AddonModSurveySyncProvider } from './providers/sync';
|
||||||
|
import { AddonModSurveySyncCronHandler } from './providers/sync-cron-handler';
|
||||||
|
import { AddonModSurveyOfflineProvider } from './providers/offline';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
AddonModSurveyComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonModSurveyProvider,
|
||||||
|
AddonModSurveyModuleHandler,
|
||||||
|
AddonModSurveyPrefetchHandler,
|
||||||
|
AddonModSurveyHelperProvider,
|
||||||
|
AddonModSurveyLinkHandler,
|
||||||
|
AddonModSurveySyncCronHandler,
|
||||||
|
AddonModSurveySyncProvider,
|
||||||
|
AddonModSurveyOfflineProvider
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonModSurveyModule {
|
||||||
|
constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModSurveyModuleHandler,
|
||||||
|
prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModSurveyPrefetchHandler,
|
||||||
|
contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModSurveyLinkHandler,
|
||||||
|
cronDelegate: CoreCronDelegate, syncHandler: AddonModSurveySyncCronHandler) {
|
||||||
|
moduleDelegate.registerHandler(moduleHandler);
|
||||||
|
prefetchDelegate.registerHandler(prefetchHandler);
|
||||||
|
contentLinksDelegate.registerHandler(linkHandler);
|
||||||
|
cronDelegate.register(syncHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,14 @@
|
||||||
.ios .core-#{$color-name}-card {
|
.ios .core-#{$color-name}-card {
|
||||||
@extend .card-ios ;
|
@extend .card-ios ;
|
||||||
@extend .card-content-ios;
|
@extend .card-content-ios;
|
||||||
|
|
||||||
|
&[icon-start] {
|
||||||
|
padding-left: $card-ios-padding-left * 2 + 20;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
left: $card-ios-padding-left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,14 @@
|
||||||
.md .core-#{$color-name}-card {
|
.md .core-#{$color-name}-card {
|
||||||
@extend .card-md;
|
@extend .card-md;
|
||||||
@extend .card-content-md;
|
@extend .card-content-md;
|
||||||
|
|
||||||
|
&[icon-start] {
|
||||||
|
padding-left: $card-md-padding-left * 2 + 20;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
left: $card-md-padding-left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ import { AddonModResourceModule } from '@addon/mod/resource/resource.module';
|
||||||
import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
|
import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
|
||||||
import { AddonModPageModule } from '@addon/mod/page/page.module';
|
import { AddonModPageModule } from '@addon/mod/page/page.module';
|
||||||
import { AddonModUrlModule } from '@addon/mod/url/url.module';
|
import { AddonModUrlModule } from '@addon/mod/url/url.module';
|
||||||
|
import { AddonModSurveyModule } from '@addon/mod/survey/survey.module';
|
||||||
import { AddonMessagesModule } from '@addon/messages/messages.module';
|
import { AddonMessagesModule } from '@addon/messages/messages.module';
|
||||||
import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module';
|
import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module';
|
||||||
import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module';
|
import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module';
|
||||||
|
@ -164,6 +165,7 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
AddonModFolderModule,
|
AddonModFolderModule,
|
||||||
AddonModPageModule,
|
AddonModPageModule,
|
||||||
AddonModUrlModule,
|
AddonModUrlModule,
|
||||||
|
AddonModSurveyModule,
|
||||||
AddonMessagesModule,
|
AddonMessagesModule,
|
||||||
AddonPushNotificationsModule,
|
AddonPushNotificationsModule,
|
||||||
AddonRemoteThemesModule
|
AddonRemoteThemesModule
|
||||||
|
|
|
@ -478,6 +478,22 @@ textarea {
|
||||||
.core-#{$color-name}-card {
|
.core-#{$color-name}-card {
|
||||||
@extend ion-card;
|
@extend ion-card;
|
||||||
border-bottom: 3px solid $color-base;
|
border-bottom: 3px solid $color-base;
|
||||||
|
|
||||||
|
&[icon-start] {
|
||||||
|
padding-left: 52px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
color: $color-base;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 16px;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,14 @@
|
||||||
.wp .core-#{$color-name}-card {
|
.wp .core-#{$color-name}-card {
|
||||||
@extend .card-wp ;
|
@extend .card-wp ;
|
||||||
@extend .card-content-wp;
|
@extend .card-content-wp;
|
||||||
|
|
||||||
|
&[icon-start] {
|
||||||
|
padding-left: $card-wp-padding-left * 2 + 20;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
left: $card-wp-padding-left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,11 +151,11 @@ export class CoreSyncBaseProvider {
|
||||||
/**
|
/**
|
||||||
* Check if a sync is needed: if a certain time has passed since the last time.
|
* Check if a sync is needed: if a certain time has passed since the last time.
|
||||||
*
|
*
|
||||||
* @param {string} id Unique sync identifier per component.
|
* @param {string | number} id Unique sync identifier per component.
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
* @return {Promise<boolean>} Promise resolved with boolean: whether sync is needed.
|
* @return {Promise<boolean>} Promise resolved with boolean: whether sync is needed.
|
||||||
*/
|
*/
|
||||||
isSyncNeeded(id: string, siteId?: string): Promise<boolean> {
|
isSyncNeeded(id: string | number, siteId?: string): Promise<boolean> {
|
||||||
return this.getSyncTime(id, siteId).then((time) => {
|
return this.getSyncTime(id, siteId).then((time) => {
|
||||||
return Date.now() - this.syncInterval >= time;
|
return Date.now() - this.syncInterval >= time;
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
// (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 { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { Network } from '@ionic-native/network';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreCourseModuleMainResourceComponent } from './main-resource-component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template class to easily create CoreCourseModuleMainComponent of activities.
|
||||||
|
*/
|
||||||
|
export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainResourceComponent {
|
||||||
|
moduleName: string; // Raw module name to be translated. It will be translated on init.
|
||||||
|
|
||||||
|
// Data for context menu.
|
||||||
|
syncIcon: string; // Sync icon.
|
||||||
|
hasOffline: boolean; // If it has offline data to be synced.
|
||||||
|
isOnline: boolean; // If the app is online or not.
|
||||||
|
|
||||||
|
protected siteId: string; // Current Site ID.
|
||||||
|
protected syncObserver: any; // It will observe the sync auto event.
|
||||||
|
protected onlineObserver: any; // It will observe the status of the network connection.
|
||||||
|
protected syncEventName: string; // Auto sync event name.
|
||||||
|
|
||||||
|
constructor(protected textUtils: CoreTextUtilsProvider, protected courseHelper: CoreCourseHelperProvider,
|
||||||
|
protected translate: TranslateService, protected domUtils: CoreDomUtilsProvider,
|
||||||
|
protected sitesProvider: CoreSitesProvider, protected courseProvider: CoreCourseProvider, network: Network,
|
||||||
|
protected appProvider: CoreAppProvider, protected eventsProvider: CoreEventsProvider) {
|
||||||
|
super(textUtils, courseHelper, translate, domUtils);
|
||||||
|
|
||||||
|
// Refresh online status when changes.
|
||||||
|
this.onlineObserver = network.onchange().subscribe((online) => {
|
||||||
|
this.isOnline = this.appProvider.isOnline();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (this.syncEventName) {
|
||||||
|
// Refresh data if this discussion is synchronized automatically.
|
||||||
|
this.syncObserver = this.eventsProvider.on(this.syncEventName, (data) => {
|
||||||
|
if (this.isRefreshSyncNeeded(data)) {
|
||||||
|
// Refresh the data.
|
||||||
|
this.refreshContent(false);
|
||||||
|
}
|
||||||
|
}, this.siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasOffline = false;
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
this.siteId = this.sitesProvider.getCurrentSiteId();
|
||||||
|
this.moduleName = this.courseProvider.translateModuleName(this.moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*
|
||||||
|
* @param {any} [refresher] Refresher.
|
||||||
|
* @param {Function} [done] Function to call when done.
|
||||||
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> {
|
||||||
|
if (this.loaded) {
|
||||||
|
this.refreshIcon = 'spinner';
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
|
||||||
|
return this.refreshContent(true, showErrors).finally(() => {
|
||||||
|
this.refreshIcon = 'refresh';
|
||||||
|
this.syncIcon = 'sync';
|
||||||
|
refresher && refresher.complete();
|
||||||
|
done && done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares sync event data with current data to check if refresh content is needed.
|
||||||
|
*
|
||||||
|
* @param {any} syncEventData Data receiven on sync observer.
|
||||||
|
* @return {boolean} True if refresh is needed, false otherwise.
|
||||||
|
*/
|
||||||
|
protected isRefreshSyncNeeded(syncEventData: any): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the refresh content function.
|
||||||
|
*
|
||||||
|
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
||||||
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> {
|
||||||
|
return this.invalidateContent().catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
}).then(() => {
|
||||||
|
return this.loadContent(true, sync, showErrors);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the component contents.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh=false] Whether we're refreshing data.
|
||||||
|
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
||||||
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the component contents and shows the corresponding error.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh=false] Whether we're refreshing data.
|
||||||
|
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
||||||
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<any> {
|
||||||
|
this.isOnline = this.appProvider.isOnline();
|
||||||
|
|
||||||
|
return this.fetchContent(refresh, sync, showErrors).catch((error) => {
|
||||||
|
if (!refresh) {
|
||||||
|
// Some call failed, retry without using cache since it might be a new activity.
|
||||||
|
return this.refreshContent(sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error getting data, fail.
|
||||||
|
this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
this.refreshIcon = 'refresh';
|
||||||
|
this.syncIcon = 'sync';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the sync of the activity.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected sync(): Promise<any> {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if sync has succeed from result sync data.
|
||||||
|
*
|
||||||
|
* @param {any} result Data returned on the sync function.
|
||||||
|
* @return {boolean} If suceed or not.
|
||||||
|
*/
|
||||||
|
protected hasSyncSucceed(result: any): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to synchronize the activity.
|
||||||
|
*
|
||||||
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with true if sync succeed, or false if failed.
|
||||||
|
*/
|
||||||
|
protected syncActivity(showErrors: boolean = false): Promise<boolean> {
|
||||||
|
return this.sync().then((result) => {
|
||||||
|
if (result.warnings && result.warnings.length) {
|
||||||
|
this.domUtils.showErrorModal(result.warnings[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hasSyncSucceed(result);
|
||||||
|
}).catch((error) => {
|
||||||
|
if (showErrors) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
super.ngOnDestroy();
|
||||||
|
|
||||||
|
this.onlineObserver && this.onlineObserver.unsubscribe();
|
||||||
|
this.syncObserver && this.syncObserver.off();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue