MOBILE-2339 feedback: Implement form page
This commit is contained in:
		
							parent
							
								
									fca428843e
								
							
						
					
					
						commit
						d8d34a786a
					
				@ -338,11 +338,11 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param {boolean} preview Preview or edit the form.
 | 
					     * @param {boolean} preview Preview or edit the form.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    gotoAnswerQuestions(preview: boolean): void {
 | 
					    gotoAnswerQuestions(preview: boolean = false): void {
 | 
				
			||||||
        const stateParams = {
 | 
					        const stateParams = {
 | 
				
			||||||
            module: this.module,
 | 
					            module: this.module,
 | 
				
			||||||
            moduleid: this.module.id,
 | 
					            moduleId: this.module.id,
 | 
				
			||||||
            courseid: this.courseId,
 | 
					            courseId: this.courseId,
 | 
				
			||||||
            preview: preview
 | 
					            preview: preview
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        this.navCtrl.push('AddonModFeedbackFormPage', stateParams);
 | 
					        this.navCtrl.push('AddonModFeedbackFormPage', stateParams);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,24 +3,32 @@
 | 
				
			|||||||
    "anonymous": "Anonymous",
 | 
					    "anonymous": "Anonymous",
 | 
				
			||||||
    "anonymous_entries": "Anonymous entries ({{$a}})",
 | 
					    "anonymous_entries": "Anonymous entries ({{$a}})",
 | 
				
			||||||
    "average": "Average",
 | 
					    "average": "Average",
 | 
				
			||||||
 | 
					    "captchaofflinewarning": "Feedback with CAPTCHA cannot be completed offline, or if not configured, or if the server is down.",
 | 
				
			||||||
    "completed_feedbacks": "Submitted answers",
 | 
					    "completed_feedbacks": "Submitted answers",
 | 
				
			||||||
    "complete_the_form": "Answer the questions...",
 | 
					    "complete_the_form": "Answer the questions...",
 | 
				
			||||||
    "continue_the_form": "Continue the form",
 | 
					    "continue_the_form": "Continue the form",
 | 
				
			||||||
    "feedbackclose": "Allow answers to",
 | 
					    "feedbackclose": "Allow answers to",
 | 
				
			||||||
    "feedbackopen": "Allow answers from",
 | 
					    "feedbackopen": "Allow answers from",
 | 
				
			||||||
    "feedback_is_not_open": "The feedback is not open",
 | 
					    "feedback_is_not_open": "The feedback is not open",
 | 
				
			||||||
 | 
					    "feedback_submitted_offline": "This feedback has been saved to be submitted later.",
 | 
				
			||||||
 | 
					    "mapcourses": "Map feedback to courses",
 | 
				
			||||||
    "mode": "Mode",
 | 
					    "mode": "Mode",
 | 
				
			||||||
 | 
					    "next_page": "Next page",
 | 
				
			||||||
    "non_anonymous": "User's name will be logged and shown with answers",
 | 
					    "non_anonymous": "User's name will be logged and shown with answers",
 | 
				
			||||||
    "non_anonymous_entries": "Non anonymous entries ({{$a}})",
 | 
					    "non_anonymous_entries": "Non anonymous entries ({{$a}})",
 | 
				
			||||||
    "non_respondents_students": "Non respondents students ({{$a}})",
 | 
					    "non_respondents_students": "Non respondents students ({{$a}})",
 | 
				
			||||||
    "not_selected": "Not selected",
 | 
					    "not_selected": "Not selected",
 | 
				
			||||||
    "not_started": "Not started",
 | 
					    "not_started": "Not started",
 | 
				
			||||||
 | 
					    "numberoutofrange": "Number out of range",
 | 
				
			||||||
    "overview": "Overview",
 | 
					    "overview": "Overview",
 | 
				
			||||||
    "page_after_submit": "Completion message",
 | 
					    "page_after_submit": "Completion message",
 | 
				
			||||||
    "preview": "Preview",
 | 
					    "preview": "Preview",
 | 
				
			||||||
 | 
					    "previous_page": "Previous page",
 | 
				
			||||||
    "questions": "Questions",
 | 
					    "questions": "Questions",
 | 
				
			||||||
    "responses": "Responses",
 | 
					    "responses": "Responses",
 | 
				
			||||||
    "response_nr": "Response number",
 | 
					    "response_nr": "Response number",
 | 
				
			||||||
 | 
					    "save_entries": "Submit your answers",
 | 
				
			||||||
 | 
					    "show_entries": "Show responses",
 | 
				
			||||||
    "show_nonrespondents": "Show non-respondents",
 | 
					    "show_nonrespondents": "Show non-respondents",
 | 
				
			||||||
    "started": "Started",
 | 
					    "started": "Started",
 | 
				
			||||||
    "this_feedback_is_already_submitted": "You've already completed this activity."
 | 
					    "this_feedback_is_already_submitted": "You've already completed this activity."
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										116
									
								
								src/addon/mod/feedback/pages/form/form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/addon/mod/feedback/pages/form/form.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-navbar>
 | 
				
			||||||
 | 
					        <ion-title><core-format-text  [text]=" title "></core-format-text></ion-title>
 | 
				
			||||||
 | 
					    </ion-navbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="feedbackLoaded">
 | 
				
			||||||
 | 
					        <ng-container *ngIf="items && items.length">
 | 
				
			||||||
 | 
					            <ion-list no-margin>
 | 
				
			||||||
 | 
					                <ion-item text-wrap>
 | 
				
			||||||
 | 
					                    <h2>{{ 'addon.mod_feedback.mode' | translate }}</h2>
 | 
				
			||||||
 | 
					                    <p *ngIf="access.isanonymous">{{ 'addon.mod_feedback.anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                    <p *ngIf="!access.isanonymous">{{ 'addon.mod_feedback.non_anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					                <ng-container *ngFor="let item of items">
 | 
				
			||||||
 | 
					                    <ion-item-divider *ngIf="item.typ == 'pagebreak'" color="light"></ion-item-divider>
 | 
				
			||||||
 | 
					                    <ion-item text-wrap *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''" [class.core-danger-item]="item.isEmpty || item.hasError">
 | 
				
			||||||
 | 
					                        <ion-label *ngIf="item.name" [core-mark-required]="item.required" stacked>
 | 
				
			||||||
 | 
					                            <span *ngIf="item.itemnumber">{{item.itemnumber}}. </span>{{ item.name }}
 | 
				
			||||||
 | 
					                        </ion-label>
 | 
				
			||||||
 | 
					                        <div item-content class="addon-mod_feedback-form-content" *ngIf="item.template">
 | 
				
			||||||
 | 
					                            <ng-container [ngSwitch]="item.template">
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'label'">
 | 
				
			||||||
 | 
					                                    <p><core-format-text [component]="component" [componentId]="componentId" [text]="item.presentation"></core-format-text></p>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'textfield'">
 | 
				
			||||||
 | 
					                                    <ion-input type="text" [(ngModel)]="item.value" autocorrect="off" name="{{item.typ}}_{{item.id}}" maxlength="{{item.maxlength}}" [required]="item.required"></ion-input>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'numeric'">
 | 
				
			||||||
 | 
					                                    <ion-input [required]="item.required" name="{{item.typ}}_{{item.id}}" type="number" [(ngModel)]="item.value"></ion-input>
 | 
				
			||||||
 | 
					                                    <p *ngIf="item.hasError" color="error">{{ 'addon.mod_feedback.numberoutofrange' |translate }} [{{item.rangefrom}}<span *ngIf="item.rangefrom && item.rangeto">, </span>{{item.rangeto}}]</p>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'textarea'">
 | 
				
			||||||
 | 
					                                    <ion-textarea [required]="item.required" name="{{item.typ}}_{{item.id}}" [attr.aria-multiline]="true" [(ngModel)]="item.value"></ion-textarea>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'multichoice-r'">
 | 
				
			||||||
 | 
					                                    <ion-list radio-group [(ngModel)]="item.value" [required]="item.required" name="{{item.typ}}_{{item.id}}">
 | 
				
			||||||
 | 
					                                        <ion-item *ngFor="let option of item.choices">
 | 
				
			||||||
 | 
					                                            <ion-label>{{option.label}}</ion-label>
 | 
				
			||||||
 | 
					                                            <ion-radio [value]="option.value"></ion-radio>
 | 
				
			||||||
 | 
					                                        </ion-item>
 | 
				
			||||||
 | 
					                                    </ion-list>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ion-list *ngSwitchCase="'multichoice-c'">
 | 
				
			||||||
 | 
					                                    <ion-item *ngFor="let option of item.choices">
 | 
				
			||||||
 | 
					                                        <ion-label>{{option.label}}</ion-label>
 | 
				
			||||||
 | 
					                                        <ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="option.checked" value="option.value"></ion-checkbox>
 | 
				
			||||||
 | 
					                                    </ion-item>
 | 
				
			||||||
 | 
					                                </ion-list>
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'multichoice-d'">
 | 
				
			||||||
 | 
					                                    <ion-select [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value">
 | 
				
			||||||
 | 
					                                        <ion-option *ngFor="let option of item.choices" [value]="option.value">{{option.label}}</ion-option>
 | 
				
			||||||
 | 
					                                    </ion-select>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ng-container *ngSwitchCase="'captcha'">
 | 
				
			||||||
 | 
					                                    <core-recaptcha *ngIf="!preview && !offline" [publicKey]="item.captcha.recaptchapublickey" [model]="item" modelValueName="value"></core-recaptcha>
 | 
				
			||||||
 | 
					                                    <div *ngIf="!preview && (!item.captcha || offline)" class="core-warning-card" icon-start>
 | 
				
			||||||
 | 
					                                        <ion-icon name="warning"></ion-icon>
 | 
				
			||||||
 | 
					                                        {{ 'addon.mod_feedback.captchaofflinewarning' | translate }}
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                            </ng-container>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </ion-item>
 | 
				
			||||||
 | 
					                </ng-container>
 | 
				
			||||||
 | 
					                <ion-grid>
 | 
				
			||||||
 | 
					                    <ion-row align-items-center>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="hasPrevPage">
 | 
				
			||||||
 | 
					                            <button ion-button block outline icon-start (click)="gotoPage(true)">
 | 
				
			||||||
 | 
					                                <ion-icon name="arrow-back"></ion-icon>
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.previous_page' | translate }}
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="hasNextPage">
 | 
				
			||||||
 | 
					                            <button ion-button block icon-end (click)="gotoPage(false)">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.next_page' | translate }}
 | 
				
			||||||
 | 
					                                <ion-icon name="arrow-forward"></ion-icon>
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="!hasNextPage">
 | 
				
			||||||
 | 
					                            <button ion-button block (click)="gotoPage(false)">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.save_entries' | translate }}
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                    </ion-row>
 | 
				
			||||||
 | 
					                </ion-grid>
 | 
				
			||||||
 | 
					            </ion-list>
 | 
				
			||||||
 | 
					        </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="core-success-card" icon-start *ngIf="completed">
 | 
				
			||||||
 | 
					            <ion-icon name="checkmark"></ion-icon>
 | 
				
			||||||
 | 
					            <p *ngIf="!completionPageContents && !completedOffline">{{ 'addon.mod_feedback.this_feedback_is_already_submitted' | translate }}</p>
 | 
				
			||||||
 | 
					            <p *ngIf="!completionPageContents && completedOffline">{{ 'addon.mod_feedback.feedback_submitted_offline' | translate }}</p>
 | 
				
			||||||
 | 
					            <p *ngIf="completionPageContents"><core-format-text  [component]="component" componentId="componentId" [text]="completionPageContents"></core-format-text></p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-grid *ngIf="completed">
 | 
				
			||||||
 | 
					            <ion-row align-items-center>
 | 
				
			||||||
 | 
					                <ion-col *ngIf="access.canviewanalysis">
 | 
				
			||||||
 | 
					                    <button ion-button block outline icon-start (click)="showAnalysis()">
 | 
				
			||||||
 | 
					                        <ion-icon name="stats"></ion-icon>
 | 
				
			||||||
 | 
					                        {{ 'addon.mod_feedback.completed_feedbacks' | translate }}
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </ion-col>
 | 
				
			||||||
 | 
					                <ion-col *ngIf="hasNextPage">
 | 
				
			||||||
 | 
					                    <button ion-button block icon-end (click)="continue()">
 | 
				
			||||||
 | 
					                        {{ 'core.continue' | translate }}
 | 
				
			||||||
 | 
					                        <ion-icon name="arrow-forward"></ion-icon>
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </ion-col>
 | 
				
			||||||
 | 
					            </ion-row>
 | 
				
			||||||
 | 
					        </ion-grid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/addon/mod/feedback/pages/form/form.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/addon/mod/feedback/pages/form/form.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					// you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					// You may obtain a copy of the License at
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					// distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { NgModule } from '@angular/core';
 | 
				
			||||||
 | 
					import { IonicPageModule } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { TranslateModule } from '@ngx-translate/core';
 | 
				
			||||||
 | 
					import { CoreDirectivesModule } from '@directives/directives.module';
 | 
				
			||||||
 | 
					import { CoreComponentsModule } from '@components/components.module';
 | 
				
			||||||
 | 
					import { CorePipesModule } from '@pipes/pipes.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackComponentsModule } from '../../components/components.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackFormPage } from './form';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackFormPage,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        CoreDirectivesModule,
 | 
				
			||||||
 | 
					        CoreComponentsModule,
 | 
				
			||||||
 | 
					        CorePipesModule,
 | 
				
			||||||
 | 
					        AddonModFeedbackComponentsModule,
 | 
				
			||||||
 | 
					        IonicPageModule.forChild(AddonModFeedbackFormPage),
 | 
				
			||||||
 | 
					        TranslateModule.forChild()
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackFormPageModule {}
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/addon/mod/feedback/pages/form/form.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/addon/mod/feedback/pages/form/form.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					page-addon-mod-feedback-form {
 | 
				
			||||||
 | 
					    .addon-mod_feedback-form-content {
 | 
				
			||||||
 | 
					        align-self: self-start;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .item-md .addon-mod_feedback-form-content {
 | 
				
			||||||
 | 
					        @include margin($item-md-padding-media-top, ($item-md-padding-end / 2), $item-md-padding-media-bottom, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .item-ios .addon-mod_feedback-form-content {
 | 
				
			||||||
 | 
					        @include margin($item-ios-padding-media-top, $item-ios-padding-start, $item-ios-padding-media-bottom, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .item-wp .addon-mod_feedback-form-content {
 | 
				
			||||||
 | 
					        @include margin($item-wp-padding-media-top, ($item-wp-padding-end / 2), $item-wp-padding-media-bottom, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										341
									
								
								src/addon/mod/feedback/pages/form/form.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/addon/mod/feedback/pages/form/form.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,341 @@
 | 
				
			|||||||
 | 
					// (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, OnDestroy, Optional } from '@angular/core';
 | 
				
			||||||
 | 
					import { IonicPage, NavParams, NavController, Content } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { Network } from '@ionic-native/network';
 | 
				
			||||||
 | 
					import { TranslateService } from '@ngx-translate/core';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from '../../providers/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelperProvider } from '../../providers/helper';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncProvider } from '../../providers/sync';
 | 
				
			||||||
 | 
					import { CoreDomUtilsProvider } from '@providers/utils/dom';
 | 
				
			||||||
 | 
					import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
				
			||||||
 | 
					import { CoreAppProvider } from '@providers/app';
 | 
				
			||||||
 | 
					import { CoreEventsProvider } from '@providers/events';
 | 
				
			||||||
 | 
					import { CoreCourseProvider } from '@core/course/providers/course';
 | 
				
			||||||
 | 
					import { CoreLoginHelperProvider } from '@core/login/providers/helper';
 | 
				
			||||||
 | 
					import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
 | 
				
			||||||
 | 
					import { CoreSitesProvider } from '@providers/sites';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays feedback form.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@IonicPage({ segment: 'addon-mod-feedback-form' })
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-form',
 | 
				
			||||||
 | 
					    templateUrl: 'form.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackFormPage implements OnDestroy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected module: any;
 | 
				
			||||||
 | 
					    protected currentPage: number;
 | 
				
			||||||
 | 
					    protected submitted: any;
 | 
				
			||||||
 | 
					    protected feedback;
 | 
				
			||||||
 | 
					    protected siteAfterSubmit;
 | 
				
			||||||
 | 
					    protected onlineObserver;
 | 
				
			||||||
 | 
					    protected originalData;
 | 
				
			||||||
 | 
					    protected currentSite;
 | 
				
			||||||
 | 
					    protected forceLeave = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    preview = false;
 | 
				
			||||||
 | 
					    courseId: number;
 | 
				
			||||||
 | 
					    componentId: number;
 | 
				
			||||||
 | 
					    completionPageContents: string;
 | 
				
			||||||
 | 
					    component = AddonModFeedbackProvider.COMPONENT;
 | 
				
			||||||
 | 
					    offline = false;
 | 
				
			||||||
 | 
					    feedbackLoaded = false;
 | 
				
			||||||
 | 
					    access: any;
 | 
				
			||||||
 | 
					    items = [];
 | 
				
			||||||
 | 
					    hasPrevPage = false;
 | 
				
			||||||
 | 
					    hasNextPage = false;
 | 
				
			||||||
 | 
					    completed = false;
 | 
				
			||||||
 | 
					    completedOffline = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(navParams: NavParams, protected feedbackProvider: AddonModFeedbackProvider, protected appProvider: CoreAppProvider,
 | 
				
			||||||
 | 
					            protected utils: CoreUtilsProvider, protected domUtils: CoreDomUtilsProvider, protected navCtrl: NavController,
 | 
				
			||||||
 | 
					            protected feedbackHelper: AddonModFeedbackHelperProvider, protected courseProvider: CoreCourseProvider,
 | 
				
			||||||
 | 
					            protected eventsProvider: CoreEventsProvider, protected feedbackSync: AddonModFeedbackSyncProvider, network: Network,
 | 
				
			||||||
 | 
					            protected translate: TranslateService, protected loginHelper: CoreLoginHelperProvider,
 | 
				
			||||||
 | 
					            protected linkHelper: CoreContentLinksHelperProvider, sitesProvider: CoreSitesProvider,
 | 
				
			||||||
 | 
					            @Optional() private content: Content) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.module = navParams.get('module');
 | 
				
			||||||
 | 
					        this.courseId = navParams.get('courseId');
 | 
				
			||||||
 | 
					        this.currentPage = navParams.get('page');
 | 
				
			||||||
 | 
					        this.title = navParams.get('title');
 | 
				
			||||||
 | 
					        this.preview = !!navParams.get('preview');
 | 
				
			||||||
 | 
					        this.componentId = navParams.get('moduleId') || this.module.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.currentSite = sitesProvider.getCurrentSite();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Refresh online status when changes.
 | 
				
			||||||
 | 
					        this.onlineObserver = network.onchange().subscribe((online) => {
 | 
				
			||||||
 | 
					            this.offline = !online;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * View loaded.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ionViewDidLoad(): void {
 | 
				
			||||||
 | 
					        this.fetchData().then(() => {
 | 
				
			||||||
 | 
					            this.feedbackProvider.logView(this.feedback.id, true).then(() => {
 | 
				
			||||||
 | 
					                this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * View entered.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ionViewDidEnter(): void {
 | 
				
			||||||
 | 
					        this.forceLeave = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if we can leave the page or not.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {boolean | Promise<void>} Resolved if we can leave it, rejected if not.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ionViewCanLeave(): boolean | Promise<void> {
 | 
				
			||||||
 | 
					        if (this.forceLeave) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.preview) {
 | 
				
			||||||
 | 
					            const responses = this.feedbackHelper.getPageItemsResponses(this.items);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.items && !this.completed && this.originalData) {
 | 
				
			||||||
 | 
					                // Form submitted. Check if there is any change.
 | 
				
			||||||
 | 
					                if (!this.utils.basicLeftCompare(responses, this.originalData, 3)) {
 | 
				
			||||||
 | 
					                     return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Promise.resolve();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch all the data required for the view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fetchData(): Promise<any> {
 | 
				
			||||||
 | 
					        this.offline = !this.appProvider.isOnline();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getFeedback(this.courseId, this.module.id).then((feedbackData) => {
 | 
				
			||||||
 | 
					            this.feedback = feedbackData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.title = this.feedback.name || this.title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.fetchAccessData();
 | 
				
			||||||
 | 
					        }).then((accessData) => {
 | 
				
			||||||
 | 
					            if (!this.preview && accessData.cansubmit && !accessData.isempty) {
 | 
				
			||||||
 | 
					                return typeof this.currentPage == 'undefined' ?
 | 
				
			||||||
 | 
					                    this.feedbackProvider.getResumePage(this.feedback.id, this.offline, true) :
 | 
				
			||||||
 | 
					                    Promise.resolve(this.currentPage);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.preview = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Promise.resolve(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).catch((error) => {
 | 
				
			||||||
 | 
					            if (!this.offline && !this.utils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                // If it fails, go offline.
 | 
				
			||||||
 | 
					                this.offline = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.feedbackProvider.getResumePage(this.feedback.id, true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.reject(error);
 | 
				
			||||||
 | 
					        }).then((page) => {
 | 
				
			||||||
 | 
					            return this.fetchFeedbackPageData(page || 0);
 | 
				
			||||||
 | 
					        }).catch((message) => {
 | 
				
			||||||
 | 
					            this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					            this.forceLeave = true;
 | 
				
			||||||
 | 
					            this.navCtrl.pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.reject(null);
 | 
				
			||||||
 | 
					        }).finally(() => {
 | 
				
			||||||
 | 
					            this.feedbackLoaded = true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch access information.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fetchAccessData(): Promise<any> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getFeedbackAccessInformation(this.feedback.id, this.offline, true).catch((error) => {
 | 
				
			||||||
 | 
					            if (!this.offline && !this.utils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                // If it fails, go offline.
 | 
				
			||||||
 | 
					                this.offline = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.feedbackProvider.getFeedbackAccessInformation(this.feedback.id, true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.reject(error);
 | 
				
			||||||
 | 
					         }).then((accessData) => {
 | 
				
			||||||
 | 
					            this.access = accessData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return accessData;
 | 
				
			||||||
 | 
					         });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected fetchFeedbackPageData(page: number = 0): Promise<void> {
 | 
				
			||||||
 | 
					        let promise;
 | 
				
			||||||
 | 
					        this.items = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.preview) {
 | 
				
			||||||
 | 
					            promise = this.feedbackProvider.getItems(this.feedback.id);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.currentPage = page;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            promise = this.feedbackProvider.getPageItemsWithValues(this.feedback.id, page, this.offline, true).catch((error) => {
 | 
				
			||||||
 | 
					                if (!this.offline && !this.utils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                    // If it fails, go offline.
 | 
				
			||||||
 | 
					                    this.offline = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return this.feedbackProvider.getPageItemsWithValues(this.feedback.id, page, true);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Promise.reject(error);
 | 
				
			||||||
 | 
					            }).then((response) => {
 | 
				
			||||||
 | 
					                this.hasPrevPage = !!response.hasprevpage;
 | 
				
			||||||
 | 
					                this.hasNextPage = !!response.hasnextpage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return response;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return promise.then((response) => {
 | 
				
			||||||
 | 
					            this.items = response.items.map((itemData) => {
 | 
				
			||||||
 | 
					                return this.feedbackHelper.getItemForm(itemData, this.preview);
 | 
				
			||||||
 | 
					            }).filter((itemData) => {
 | 
				
			||||||
 | 
					                // Filter items with errors.
 | 
				
			||||||
 | 
					                return itemData;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!this.preview) {
 | 
				
			||||||
 | 
					                const itemsCopy = this.utils.clone(this.items); // Copy the array to avoid modifications.
 | 
				
			||||||
 | 
					                this.originalData = this.feedbackHelper.getPageItemsResponses(itemsCopy);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to allow page navigation through the questions form.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {boolean}       goPrevious If true it will go back to the previous page, if false, it will go forward.
 | 
				
			||||||
 | 
					     * @return {Promise<void>}            Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    gotoPage(goPrevious: boolean): Promise<void> {
 | 
				
			||||||
 | 
					        this.content && this.content.scrollToTop();
 | 
				
			||||||
 | 
					        this.feedbackLoaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const responses = this.feedbackHelper.getPageItemsResponses(this.items),
 | 
				
			||||||
 | 
					            formHasErrors = this.items.some((item) => {
 | 
				
			||||||
 | 
					                return item.isEmpty || item.hasError;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Sync other pages first.
 | 
				
			||||||
 | 
					        return this.feedbackSync.syncFeedback(this.feedback.id).catch(() => {
 | 
				
			||||||
 | 
					            // Ignore errors.
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            return this.feedbackProvider.processPage(this.feedback.id, this.currentPage, responses, goPrevious, formHasErrors,
 | 
				
			||||||
 | 
					                    this.courseId).then((response) => {
 | 
				
			||||||
 | 
					                const jumpTo = parseInt(response.jumpto, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (response.completed) {
 | 
				
			||||||
 | 
					                    // Form is completed, show completion message and buttons.
 | 
				
			||||||
 | 
					                    this.items = [];
 | 
				
			||||||
 | 
					                    this.completed = true;
 | 
				
			||||||
 | 
					                    this.completedOffline = !!response.offline;
 | 
				
			||||||
 | 
					                    this.completionPageContents = response.completionpagecontents;
 | 
				
			||||||
 | 
					                    this.siteAfterSubmit = response.siteaftersubmit;
 | 
				
			||||||
 | 
					                    this.submitted = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Invalidate access information so user will see home page updated (continue form or completion messages).
 | 
				
			||||||
 | 
					                    const promises = [];
 | 
				
			||||||
 | 
					                    promises.push(this.feedbackProvider.invalidateFeedbackAccessInformationData(this.feedback.id));
 | 
				
			||||||
 | 
					                    promises.push(this.feedbackProvider.invalidateResumePageData(this.feedback.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return Promise.all(promises).then(() => {
 | 
				
			||||||
 | 
					                        return this.fetchAccessData();
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else if (isNaN(jumpTo) || jumpTo == this.currentPage) {
 | 
				
			||||||
 | 
					                    // Errors on questions, stay in page.
 | 
				
			||||||
 | 
					                    return Promise.resolve();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.submitted = true;
 | 
				
			||||||
 | 
					                    // Invalidate access information so user will see home page updated (continue form).
 | 
				
			||||||
 | 
					                    this.feedbackProvider.invalidateResumePageData(this.feedback.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Fetch the new page.
 | 
				
			||||||
 | 
					                    return this.fetchFeedbackPageData(jumpTo);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }).catch((message) => {
 | 
				
			||||||
 | 
					            this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.reject(null);
 | 
				
			||||||
 | 
					        }).finally(() => {
 | 
				
			||||||
 | 
					            this.feedbackLoaded = true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to link implemented features.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    showAnalysis(): void {
 | 
				
			||||||
 | 
					        this.submitted = 'analysis';
 | 
				
			||||||
 | 
					        this.feedbackHelper.openFeature('analysis', this.navCtrl, this.module, this.courseId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to go to the page after submit.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    continue(): void {
 | 
				
			||||||
 | 
					        if (this.siteAfterSubmit) {
 | 
				
			||||||
 | 
					            const modal = this.domUtils.showModalLoading();
 | 
				
			||||||
 | 
					            this.linkHelper.handleLink(this.siteAfterSubmit).then((treated) => {
 | 
				
			||||||
 | 
					                if (!treated) {
 | 
				
			||||||
 | 
					                    return this.currentSite.openInBrowserWithAutoLoginIfSameSite(this.siteAfterSubmit);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }).finally(() => {
 | 
				
			||||||
 | 
					                modal.dismiss();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Use redirect to make the course the new history root (to avoid "loops" in history).
 | 
				
			||||||
 | 
					            this.loginHelper.redirect('CoreCourseSectionPage', {
 | 
				
			||||||
 | 
					                course: { id: this.courseId }
 | 
				
			||||||
 | 
					            }, this.currentSite.getId());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being destroyed.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnDestroy(): void {
 | 
				
			||||||
 | 
					        if (this.submitted) {
 | 
				
			||||||
 | 
					            const tab = this.submitted == 'analysis' ? 'analysis' : 'overview';
 | 
				
			||||||
 | 
					            // If form has been submitted, the info has been already invalidated but we should update index view.
 | 
				
			||||||
 | 
					            this.eventsProvider.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {feedbackId: this.feedback.id, tab: tab});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.onlineObserver && this.onlineObserver.unsubscribe();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -17,6 +17,8 @@ import { CoreLoggerProvider } from '@providers/logger';
 | 
				
			|||||||
import { CoreSitesProvider } from '@providers/sites';
 | 
					import { CoreSitesProvider } from '@providers/sites';
 | 
				
			||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
					import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
				
			||||||
import { CoreFilepoolProvider } from '@providers/filepool';
 | 
					import { CoreFilepoolProvider } from '@providers/filepool';
 | 
				
			||||||
 | 
					import { CoreAppProvider } from '@providers/app';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOfflineProvider } from './offline';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service that provides some features for feedbacks.
 | 
					 * Service that provides some features for feedbacks.
 | 
				
			||||||
@ -35,10 +37,255 @@ export class AddonModFeedbackProvider {
 | 
				
			|||||||
    protected logger;
 | 
					    protected logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
 | 
					    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
 | 
				
			||||||
            private filepoolProvider: CoreFilepoolProvider) {
 | 
					            private filepoolProvider: CoreFilepoolProvider, private feedbackOffline: AddonModFeedbackOfflineProvider,
 | 
				
			||||||
 | 
					            private appProvider: CoreAppProvider) {
 | 
				
			||||||
        this.logger = logger.getInstance('AddonModFeedbackProvider');
 | 
					        this.logger = logger.getInstance('AddonModFeedbackProvider');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check dependency of a question item.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {any[]}  items      All question items to check dependency.
 | 
				
			||||||
 | 
					     * @param   {any}    item       Item to check.
 | 
				
			||||||
 | 
					     * @return  {boolean}           Return true if dependency is acomplished and it can be shown. False, otherwise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected checkDependencyItem(items: any[], item: any): boolean {
 | 
				
			||||||
 | 
					        const depend = items.find((itemFind) => {
 | 
				
			||||||
 | 
					            return itemFind.id == item.dependitem;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Item not found, looks like dependent item has been removed or is in the same or following pages.
 | 
				
			||||||
 | 
					        if (!depend) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (depend.typ) {
 | 
				
			||||||
 | 
					            case 'label':
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            case 'multichoice':
 | 
				
			||||||
 | 
					            case 'multichoicerated':
 | 
				
			||||||
 | 
					                return this.compareDependItemMultichoice(depend, item.dependvalue);
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return item.dependvalue == depend.rawValue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check dependency item of type Multichoice.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {any}     item        Item to check.
 | 
				
			||||||
 | 
					     * @param  {string}  dependValue Value to compare.
 | 
				
			||||||
 | 
					     * @return {boolean}             eturn true if dependency is acomplished and it can be shown. False, otherwise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected compareDependItemMultichoice(item: any, dependValue: string): boolean {
 | 
				
			||||||
 | 
					        let values, choices;
 | 
				
			||||||
 | 
					        const parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP) || [],
 | 
				
			||||||
 | 
					            subtype = parts.length > 0 && parts[0] ? parts[0] : 'r';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        choices = parts[1] || '';
 | 
				
			||||||
 | 
					        choices = choices.split(AddonModFeedbackProvider.MULTICHOICE_ADJUST_SEP)[0] || '';
 | 
				
			||||||
 | 
					        choices = choices.split(AddonModFeedbackProvider.LINE_SEP) || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (subtype === 'c') {
 | 
				
			||||||
 | 
					            if (typeof item.rawValue == 'undefined') {
 | 
				
			||||||
 | 
					                values = [''];
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                item.rawValue = '' + item.rawValue;
 | 
				
			||||||
 | 
					                values = item.rawValue.split(AddonModFeedbackProvider.LINE_SEP);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            values = [item.rawValue];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let index = 0; index < choices.length; index++) {
 | 
				
			||||||
 | 
					            for (const x in values) {
 | 
				
			||||||
 | 
					                if (values[x] == index + 1) {
 | 
				
			||||||
 | 
					                    let value = choices[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (item.typ == 'multichoicerated') {
 | 
				
			||||||
 | 
					                        value = value.split(AddonModFeedbackProvider.MULTICHOICERATED_VALUE_SEP)[1] || '';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (value.trim() == dependValue) {
 | 
				
			||||||
 | 
					                        return true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // We can finish checking if only searching on one value and we found it.
 | 
				
			||||||
 | 
					                    if (values.length == 1) {
 | 
				
			||||||
 | 
					                        return false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fill values of item questions.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}   feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {any[]}    items        Item to fill the value.
 | 
				
			||||||
 | 
					     * @param   {boolean}  offline      True if it should return cached data. Has priority over ignoreCache.
 | 
				
			||||||
 | 
					     * @param   {boolean}  ignoreCache  True if it should ignore cached data (it will always fail in offline or server down).
 | 
				
			||||||
 | 
					     * @param   {string}   siteId       Site ID.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}          Resolved with values when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fillValues(feedbackId: number, items: any[], offline: boolean, ignoreCache: boolean, siteId: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.getCurrentValues(feedbackId, offline, ignoreCache, siteId).then((valuesArray) => {
 | 
				
			||||||
 | 
					            if (valuesArray.length == 0) {
 | 
				
			||||||
 | 
					                // Try sending empty values to get the last completed attempt values.
 | 
				
			||||||
 | 
					                return this.processPageOnline(feedbackId, 0, {}, undefined, siteId).then(() => {
 | 
				
			||||||
 | 
					                    return this.getCurrentValues(feedbackId, offline, ignoreCache, siteId);
 | 
				
			||||||
 | 
					                }).catch(() => {
 | 
				
			||||||
 | 
					                    // Ignore errors
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return valuesArray;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }).then((valuesArray) => {
 | 
				
			||||||
 | 
					            const values = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            valuesArray.forEach((value) => {
 | 
				
			||||||
 | 
					                values[value.item] = value.value;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            items.forEach((itemData) => {
 | 
				
			||||||
 | 
					                if (itemData.hasvalue && typeof values[itemData.id] != 'undefined') {
 | 
				
			||||||
 | 
					                    itemData.rawValue = values[itemData.id];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }).catch(() => {
 | 
				
			||||||
 | 
					            // Ignore errors.
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            // Merge with offline data.
 | 
				
			||||||
 | 
					            return this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).then((offlineValuesArray) => {
 | 
				
			||||||
 | 
					                const offlineValues = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Merge all values into one array.
 | 
				
			||||||
 | 
					                offlineValuesArray = offlineValuesArray.reduce((a, b) => {
 | 
				
			||||||
 | 
					                    const responses = this.utils.objectToArrayOfObjects(b.responses, 'id', 'value');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return a.concat(responses);
 | 
				
			||||||
 | 
					                }, []).map((a) => {
 | 
				
			||||||
 | 
					                    const parts = a.id.split('_');
 | 
				
			||||||
 | 
					                    a.typ = parts[0];
 | 
				
			||||||
 | 
					                    a.item = parseInt(parts[1], 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return a;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                offlineValuesArray.forEach((value) => {
 | 
				
			||||||
 | 
					                    if (typeof offlineValues[value.item] == 'undefined') {
 | 
				
			||||||
 | 
					                        offlineValues[value.item] = [];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    offlineValues[value.item].push(value.value);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                items.forEach((itemData) => {
 | 
				
			||||||
 | 
					                    if (itemData.hasvalue && typeof offlineValues[itemData.id] != 'undefined') {
 | 
				
			||||||
 | 
					                        // Treat multichoice checkboxes.
 | 
				
			||||||
 | 
					                        if (itemData.typ == 'multichoice' &&
 | 
				
			||||||
 | 
					                                itemData.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP)[0] == 'c') {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            offlineValues[itemData.id] = offlineValues[itemData.id].filter((value) => {
 | 
				
			||||||
 | 
					                                return value > 0;
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                            itemData.rawValue = offlineValues[itemData.id].join(AddonModFeedbackProvider.LINE_SEP);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            itemData.rawValue = offlineValues[itemData.id][0];
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return items;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }).catch(() => {
 | 
				
			||||||
 | 
					            // Ignore errors.
 | 
				
			||||||
 | 
					            return items;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns all the feedback non respondents users.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}    groupId         Group id, 0 means that the function will determine the user group.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @param   {any}       [previous]      Only for recurrent use. Object with the previous fetched info.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}              Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getAllNonRespondents(feedbackId: number, groupId: number, siteId?: string, previous?: any): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					        if (typeof previous == 'undefined') {
 | 
				
			||||||
 | 
					            previous = {
 | 
				
			||||||
 | 
					                page: 0,
 | 
				
			||||||
 | 
					                users: []
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.getNonRespondents(feedbackId, groupId, previous.page, siteId).then((response) => {
 | 
				
			||||||
 | 
					            if (previous.users.length < response.total) {
 | 
				
			||||||
 | 
					                previous.users = previous.users.concat(response.users);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (previous.users.length < response.total) {
 | 
				
			||||||
 | 
					                // Can load more.
 | 
				
			||||||
 | 
					                previous.page++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.getAllNonRespondents(feedbackId, groupId, siteId, previous);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            previous.total = response.total;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return previous;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns all the feedback user responses.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}    groupId         Group id, 0 means that the function will determine the user group.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @param   {any}       [previous]      Only for recurrent use. Object with the previous fetched info.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}              Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getAllResponsesAnalysis(feedbackId: number, groupId: number, siteId?: string, previous?: any): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					        if (typeof previous == 'undefined') {
 | 
				
			||||||
 | 
					            previous = {
 | 
				
			||||||
 | 
					                page: 0,
 | 
				
			||||||
 | 
					                attempts: [],
 | 
				
			||||||
 | 
					                anonattempts: []
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.getResponsesAnalysis(feedbackId, groupId, previous.page, siteId).then((responses) => {
 | 
				
			||||||
 | 
					            if (previous.anonattempts.length < responses.totalanonattempts) {
 | 
				
			||||||
 | 
					                previous.anonattempts = previous.anonattempts.concat(responses.anonattempts);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (previous.attempts.length < responses.totalattempts) {
 | 
				
			||||||
 | 
					                previous.attempts = previous.attempts.concat(responses.attempts);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (previous.anonattempts.length < responses.totalanonattempts || previous.attempts.length < responses.totalattempts) {
 | 
				
			||||||
 | 
					                // Can load more.
 | 
				
			||||||
 | 
					                previous.page++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.getAllResponsesAnalysis(feedbackId, groupId, siteId, previous);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            previous.totalattempts = responses.totalattempts;
 | 
				
			||||||
 | 
					            previous.totalanonattempts = responses.totalanonattempts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return previous;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get analysis information for a given feedback.
 | 
					     * Get analysis information for a given feedback.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -436,6 +683,118 @@ export class AddonModFeedbackProvider {
 | 
				
			|||||||
        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':nonrespondents:';
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':nonrespondents:';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a single feedback page items. This function is not cached, use AddonModFeedbackHelperProvider#getPageItems instead.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId  Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}    page        The page to get.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]    Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}          Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPageItems(feedbackId: number, page: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId,
 | 
				
			||||||
 | 
					                    page: page
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.write('mod_feedback_get_page_items', params);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a single feedback page items. If offline or server down it will use getItems to calculate dependencies.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}  feedbackId          Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}  page                The page to get.
 | 
				
			||||||
 | 
					     * @param   {boolean} [offline=false]     True if it should return cached data. Has priority over ignoreCache.
 | 
				
			||||||
 | 
					     * @param   {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
 | 
				
			||||||
 | 
					     * @param   {string}  [siteId]            Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPageItemsWithValues(feedbackId: number, page: number, offline: boolean = false, ignoreCache: boolean = false,
 | 
				
			||||||
 | 
					            siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.getPageItems(feedbackId, page, siteId).then((response) => {
 | 
				
			||||||
 | 
					            return this.fillValues(feedbackId, response.items, offline, ignoreCache, siteId).then((items) => {
 | 
				
			||||||
 | 
					                response.items = items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return response;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }).catch(() => {
 | 
				
			||||||
 | 
					            // If getPageItems fail we should calculate it using getItems.
 | 
				
			||||||
 | 
					            return this.getItems(feedbackId, siteId).then((response) => {
 | 
				
			||||||
 | 
					                return this.fillValues(feedbackId, response.items, offline, ignoreCache, siteId).then((items) => {
 | 
				
			||||||
 | 
					                    // Separate items by pages.
 | 
				
			||||||
 | 
					                    let currentPage = 0;
 | 
				
			||||||
 | 
					                    const previousPageItems = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const pageItems = items.filter((item) => {
 | 
				
			||||||
 | 
					                        // Greater page, discard all entries.
 | 
				
			||||||
 | 
					                        if (currentPage > page) {
 | 
				
			||||||
 | 
					                            return false;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (item.typ == 'pagebreak') {
 | 
				
			||||||
 | 
					                            currentPage++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return false;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Save items on previous page to check dependencies and discard entry.
 | 
				
			||||||
 | 
					                        if (currentPage < page) {
 | 
				
			||||||
 | 
					                            previousPageItems.push(item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return false;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Filter depending items.
 | 
				
			||||||
 | 
					                        if (item && item.dependitem > 0 && previousPageItems.length > 0) {
 | 
				
			||||||
 | 
					                            return this.checkDependencyItem(previousPageItems, item);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Filter items with errors.
 | 
				
			||||||
 | 
					                        return item;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Check if there are more pages.
 | 
				
			||||||
 | 
					                    response.hasprevpage = page > 0;
 | 
				
			||||||
 | 
					                    response.hasnextpage = currentPage > page;
 | 
				
			||||||
 | 
					                    response.items = pageItems;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return response;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Convenience function to get the page we can jump.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number}  feedbackId [description]
 | 
				
			||||||
 | 
					     * @param  {number}  page       [description]
 | 
				
			||||||
 | 
					     * @param  {number}  changePage [description]
 | 
				
			||||||
 | 
					     * @param  {string}  siteId     [description]
 | 
				
			||||||
 | 
					     * @return {Promise<number | false>}            [description]
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getPageJumpTo(feedbackId: number, page: number, changePage: number, siteId: string): Promise<number | false> {
 | 
				
			||||||
 | 
					        return this.getPageItemsWithValues(feedbackId, page, true, false, siteId).then((resp) => {
 | 
				
			||||||
 | 
					            // The page we are going has items.
 | 
				
			||||||
 | 
					            if (resp.items.length > 0) {
 | 
				
			||||||
 | 
					                return page;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check we can jump futher.
 | 
				
			||||||
 | 
					            if ((changePage == 1 && resp.hasnextpage) || (changePage == -1 && resp.hasprevpage)) {
 | 
				
			||||||
 | 
					                return this.getPageJumpTo(feedbackId, page + changePage, changePage, siteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Completed or first page.
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the feedback user responses.
 | 
					     * Returns the feedback user responses.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -723,6 +1082,93 @@ export class AddonModFeedbackProvider {
 | 
				
			|||||||
        return this.sitesProvider.getCurrentSite().write('mod_feedback_view_feedback', params);
 | 
					        return this.sitesProvider.getCurrentSite().write('mod_feedback_view_feedback', params);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Process a jump between pages.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}    page            The page being processed.
 | 
				
			||||||
 | 
					     * @param   {any}       responses       The data to be processed the key is the field name (usually type[index]_id).
 | 
				
			||||||
 | 
					     * @param   {boolean}   goPrevious      Whether we want to jump to previous page.
 | 
				
			||||||
 | 
					     * @param   {boolean}   formHasErrors   Whether the form we sent has required but empty fields (only used in offline).
 | 
				
			||||||
 | 
					     * @param   {number}    courseId        Course ID the feedback belongs to.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                   Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    processPage(feedbackId: number, page: number, responses: any, goPrevious: boolean, formHasErrors: boolean, courseId: number,
 | 
				
			||||||
 | 
					            siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Convenience function to store a message to be synchronized later.
 | 
				
			||||||
 | 
					        const storeOffline = (): Promise<any> => {
 | 
				
			||||||
 | 
					            return this.feedbackOffline.saveResponses(feedbackId, page, responses, courseId, siteId).then(() => {
 | 
				
			||||||
 | 
					                // Simulate process_page response.
 | 
				
			||||||
 | 
					                const response = {
 | 
				
			||||||
 | 
					                        jumpto: page,
 | 
				
			||||||
 | 
					                        completed: false,
 | 
				
			||||||
 | 
					                        offline: true
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                let changePage = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (goPrevious) {
 | 
				
			||||||
 | 
					                    if (page > 0) {
 | 
				
			||||||
 | 
					                        changePage = -1;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else if (!formHasErrors) {
 | 
				
			||||||
 | 
					                    // We can only go next if it has no errors.
 | 
				
			||||||
 | 
					                    changePage = 1;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (changePage === 0) {
 | 
				
			||||||
 | 
					                    return response;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.getPageItemsWithValues(feedbackId, page, true, false, siteId).then((resp) => {
 | 
				
			||||||
 | 
					                    // Check completion.
 | 
				
			||||||
 | 
					                    if (changePage == 1 && !resp.hasnextpage) {
 | 
				
			||||||
 | 
					                        response.completed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return response;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return this.getPageJumpTo(feedbackId, page + changePage, changePage, siteId).then((loadPage) => {
 | 
				
			||||||
 | 
					                        if (loadPage === false) {
 | 
				
			||||||
 | 
					                            // Completed or first page.
 | 
				
			||||||
 | 
					                            if (changePage == -1) {
 | 
				
			||||||
 | 
					                                // First page.
 | 
				
			||||||
 | 
					                                response.jumpto = 0;
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                // Completed.
 | 
				
			||||||
 | 
					                                response.completed = true;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            response.jumpto = loadPage;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return response;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.appProvider.isOnline()) {
 | 
				
			||||||
 | 
					            // App is offline, store the action.
 | 
				
			||||||
 | 
					            return storeOffline();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If there's already a response to be sent to the server, discard it first.
 | 
				
			||||||
 | 
					        return this.feedbackOffline.deleteFeedbackPageResponses(feedbackId, page, siteId).then(() => {
 | 
				
			||||||
 | 
					            return this.processPageOnline(feedbackId, page, responses, goPrevious, siteId).catch((error) => {
 | 
				
			||||||
 | 
					                if (this.utils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                    // The WebService has thrown an error, this means that responses cannot be submitted.
 | 
				
			||||||
 | 
					                    return Promise.reject(error);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Couldn't connect to server, store in offline.
 | 
				
			||||||
 | 
					                return storeOffline();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Process a jump between pages.
 | 
					     * Process a jump between pages.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -94,6 +94,88 @@ export class AddonModFeedbackHelperProvider {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get page items responses to be sent.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {any[]} items    Items where the values are.
 | 
				
			||||||
 | 
					     * @return  {any}            Responses object to be sent.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPageItemsResponses(items: any[]): any {
 | 
				
			||||||
 | 
					        const responses = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        items.forEach((itemData) => {
 | 
				
			||||||
 | 
					            let answered = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            itemData.hasError = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (itemData.typ == 'captcha') {
 | 
				
			||||||
 | 
					                const value = itemData.value || '',
 | 
				
			||||||
 | 
					                    name = itemData.typ + '_' + itemData.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                answered = !!value;
 | 
				
			||||||
 | 
					                responses[name] = 1;
 | 
				
			||||||
 | 
					                responses['g-recaptcha-response'] = value;
 | 
				
			||||||
 | 
					                responses['recaptcha_element'] = 'dummyvalue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemData.required && !answered) {
 | 
				
			||||||
 | 
					                    // Check if it has any value.
 | 
				
			||||||
 | 
					                    itemData.isEmpty = true;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    itemData.isEmpty = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if (itemData.hasvalue) {
 | 
				
			||||||
 | 
					                let name, value;
 | 
				
			||||||
 | 
					                const nameTemp = itemData.typ + '_' + itemData.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemData.typ == 'multichoice' && itemData.subtype == 'c') {
 | 
				
			||||||
 | 
					                    name = nameTemp + '[0]';
 | 
				
			||||||
 | 
					                    responses[name] = 0;
 | 
				
			||||||
 | 
					                    itemData.choices.forEach((choice, index) => {
 | 
				
			||||||
 | 
					                        name = nameTemp + '[' + (index + 1) + ']';
 | 
				
			||||||
 | 
					                        value = choice.checked ? choice.value : 0;
 | 
				
			||||||
 | 
					                        if (!answered && value) {
 | 
				
			||||||
 | 
					                            answered = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        responses[name] = value;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (itemData.typ == 'multichoice') {
 | 
				
			||||||
 | 
					                        name = nameTemp + '[0]';
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        name = nameTemp;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (itemData.typ == 'multichoice' || itemData.typ == 'multichoicerated') {
 | 
				
			||||||
 | 
					                        value = itemData.value || 0;
 | 
				
			||||||
 | 
					                    } else if (itemData.typ == 'numeric') {
 | 
				
			||||||
 | 
					                        value = itemData.value || itemData.value  == 0 ? itemData.value : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (value != '') {
 | 
				
			||||||
 | 
					                            if ((itemData.rangefrom != '' && value < itemData.rangefrom) ||
 | 
				
			||||||
 | 
					                                    (itemData.rangeto != '' && value > itemData.rangeto)) {
 | 
				
			||||||
 | 
					                                itemData.hasError = true;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        value = itemData.value || itemData.value  == 0 ? itemData.value : '';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    answered = !!value;
 | 
				
			||||||
 | 
					                    responses[name] = value;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemData.required && !answered) {
 | 
				
			||||||
 | 
					                    // Check if it has any value.
 | 
				
			||||||
 | 
					                    itemData.isEmpty = true;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    itemData.isEmpty = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return responses;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the feedback user responses with extra info.
 | 
					     * Returns the feedback user responses with extra info.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -269,8 +351,6 @@ export class AddonModFeedbackHelperProvider {
 | 
				
			|||||||
            parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_ADJUST_SEP) || [];
 | 
					            parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_ADJUST_SEP) || [];
 | 
				
			||||||
            item.presentation = parts.length > 0 ? parts[0] : '';
 | 
					            item.presentation = parts.length > 0 ? parts[0] : '';
 | 
				
			||||||
            // Horizontal are not supported right now. item.horizontal = parts.length > 1 && !!parts[1];
 | 
					            // Horizontal are not supported right now. item.horizontal = parts.length > 1 && !!parts[1];
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            item.class = 'item-select';
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        item.choices = item.presentation.split(AddonModFeedbackProvider.LINE_SEP) || [];
 | 
					        item.choices = item.presentation.split(AddonModFeedbackProvider.LINE_SEP) || [];
 | 
				
			||||||
@ -320,9 +400,6 @@ export class AddonModFeedbackHelperProvider {
 | 
				
			|||||||
        const data = this.textUtils.parseJSON(item.otherdata);
 | 
					        const data = this.textUtils.parseJSON(item.otherdata);
 | 
				
			||||||
        if (data && data.length > 3) {
 | 
					        if (data && data.length > 3) {
 | 
				
			||||||
            item.captcha = {
 | 
					            item.captcha = {
 | 
				
			||||||
                challengehash: data[0],
 | 
					 | 
				
			||||||
                imageurl: data[1],
 | 
					 | 
				
			||||||
                jsurl: data[2],
 | 
					 | 
				
			||||||
                recaptchapublickey: data[3]
 | 
					                recaptchapublickey: data[3]
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -157,7 +157,7 @@ export class AddonModFeedbackOfflineProvider {
 | 
				
			|||||||
                    timemodified: this.timeUtils.timestamp()
 | 
					                    timemodified: this.timeUtils.timestamp()
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return site.getDb().insertOrUpdateRecord(this.FEEDBACK_TABLE, entry, {feedbackid: feedbackId, page: page});
 | 
					            return site.getDb().insertRecord(this.FEEDBACK_TABLE, entry);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -47,7 +47,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseModulePrefetchHan
 | 
				
			|||||||
     *                           in the filepool root feedback.
 | 
					     *                           in the filepool root feedback.
 | 
				
			||||||
     * @return {Promise<any>} Promise resolved when all content is downloaded. Data returned is not reliable.
 | 
					     * @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> {
 | 
					    downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise<any> {
 | 
				
			||||||
        const promises = [],
 | 
					        const promises = [],
 | 
				
			||||||
            siteId = this.sitesProvider.getCurrentSiteId();
 | 
					            siteId = this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,7 +119,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseModulePrefetchHan
 | 
				
			|||||||
        }));
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Promise.all(promises);
 | 
					        return Promise.all(promises);
 | 
				
			||||||
    }*/
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the list of downloadable files.
 | 
					     * Get the list of downloadable files.
 | 
				
			||||||
@ -129,7 +129,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseModulePrefetchHan
 | 
				
			|||||||
     * @param  {string} [siteId]  Site ID. If not defined, current site.
 | 
					     * @param  {string} [siteId]  Site ID. If not defined, current site.
 | 
				
			||||||
     * @return {Promise<any>}     Promise resolved with the list of files.
 | 
					     * @return {Promise<any>}     Promise resolved with the list of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    /*getFiles(module: any, courseId: number, single?: boolean): Promise<any[]> {
 | 
					    getFiles(module: any, courseId: number, single?: boolean): Promise<any[]> {
 | 
				
			||||||
        let files = [];
 | 
					        let files = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.feedbackProvider.getFeedback(courseId, module.id).then((feedback) => {
 | 
					        return this.feedbackProvider.getFeedback(courseId, module.id).then((feedback) => {
 | 
				
			||||||
@ -149,7 +149,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseModulePrefetchHan
 | 
				
			|||||||
            // Any error, return the list we have.
 | 
					            // Any error, return the list we have.
 | 
				
			||||||
            return files;
 | 
					            return files;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }*/
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns feedback intro files.
 | 
					     * Returns feedback intro files.
 | 
				
			||||||
 | 
				
			|||||||
@ -567,8 +567,17 @@ textarea {
 | 
				
			|||||||
    height: auto;
 | 
					    height: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Message cards
 | 
					canvas[core-chart] {
 | 
				
			||||||
 | 
					  max-width: 500px;
 | 
				
			||||||
 | 
					  margin: 0 auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.core-circle:before {
 | 
				
			||||||
 | 
					  content: ' \25CF';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@each $color-name, $color-base, $color-contrast in get-colors($colors) {
 | 
					@each $color-name, $color-base, $color-contrast in get-colors($colors) {
 | 
				
			||||||
 | 
					  // Message cards.
 | 
				
			||||||
  .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;
 | 
				
			||||||
@ -589,21 +598,18 @@ textarea {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
canvas[core-chart] {
 | 
					  .core-#{$color-name}-item {
 | 
				
			||||||
  max-width: 500px;
 | 
					    border-bottom: 3px solid $color-base !important;
 | 
				
			||||||
  margin: 0 auto;
 | 
					    ion-icon {
 | 
				
			||||||
}
 | 
					      color: $color-base;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.core-circle:before {
 | 
					 | 
				
			||||||
  content: ' \25CF';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@each $color-name, $color-base, $color-contrast in get-colors($colors) {
 | 
					 | 
				
			||||||
  .core-#{$color-name}-circle {
 | 
					  .core-#{$color-name}-circle {
 | 
				
			||||||
    margin: 0 4px;
 | 
					    margin: 0 4px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .core-#{$color-name}-circle:before {
 | 
					  .core-#{$color-name}-circle:before {
 | 
				
			||||||
    @extend .core-circle:before;
 | 
					    @extend .core-circle:before;
 | 
				
			||||||
    color: $color-base;
 | 
					    color: $color-base;
 | 
				
			||||||
 | 
				
			|||||||
@ -59,7 +59,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Refresh online status when changes.
 | 
					        // Refresh online status when changes.
 | 
				
			||||||
        this.onlineObserver = network.onchange().subscribe((online) => {
 | 
					        this.onlineObserver = network.onchange().subscribe((online) => {
 | 
				
			||||||
            this.isOnline = this.appProvider.isOnline();
 | 
					            this.isOnline = online;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -170,9 +170,10 @@ export class CoreUtilsProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Blocks leaving a view.
 | 
					     * Blocks leaving a view.
 | 
				
			||||||
 | 
					     * @deprecated, use ionViewCanLeave instead.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    blockLeaveView(): void {
 | 
					    blockLeaveView(): void {
 | 
				
			||||||
        // @todo
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user