MOBILE-3323 editor: Delete draft when form is submitted

main
Dani Palou 2020-01-31 14:53:13 +01:00
parent 5a79151b01
commit 7fa8e6fe05
58 changed files with 608 additions and 154 deletions

View File

@ -9,7 +9,7 @@
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<form ion-list [formGroup]="eventForm" *ngIf="!error"> <form ion-list [formGroup]="eventForm" *ngIf="!error" #editEventForm>
<!-- Event name. --> <!-- Event name. -->
<ion-item text-wrap> <ion-item text-wrap>
<ion-label stacked><h2 [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</h2></ion-label> <ion-label stacked><h2 [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</h2></ion-label>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -44,6 +44,7 @@ import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
export class AddonCalendarEditEventPage implements OnInit, OnDestroy { export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
@ViewChild(CoreEditorRichTextEditorComponent) descriptionEditor: CoreEditorRichTextEditorComponent; @ViewChild(CoreEditorRichTextEditorComponent) descriptionEditor: CoreEditorRichTextEditorComponent;
@ViewChild('editEventForm') formElement: ElementRef;
title: string; title: string;
dateFormat: string; dateFormat: string;
@ -496,6 +497,11 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.calendarProvider.submitEvent(this.eventId, data).then((result) => { this.calendarProvider.submitEvent(this.eventId, data).then((result) => {
event = result.event; event = result.event;
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: result.sent,
}, this.currentSite.getId());
if (result.sent) { if (result.sent) {
// Event created or edited, invalidate right days & months. // Event created or edited, invalidate right days & months.
const numberOfRepetitions = formData.repeat ? formData.repeats : const numberOfRepetitions = formData.repeat ? formData.repeats :

View File

@ -9,7 +9,7 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<form name="addon-mod_assign-edit-feedback-form" *ngIf="userId && plugin"> <form name="addon-mod_assign-edit-feedback-form" *ngIf="userId && plugin" #editFeedbackForm>
<addon-mod-assign-feedback-plugin [assign]="assign" [submission]="submission" [userId]="userId" [plugin]="plugin" [edit]="true"></addon-mod-assign-feedback-plugin> <addon-mod-assign-feedback-plugin [assign]="assign" [submission]="submission" [userId]="userId" [plugin]="plugin" [edit]="true"></addon-mod-assign-feedback-plugin>
<button ion-button block (click)="done($event)">{{ 'core.done' | translate }}</button> <button ion-button block (click)="done($event)">{{ 'core.done' | translate }}</button>
</form> </form>

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input } from '@angular/core'; import { Component, Input, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, ViewController, NavParams } from 'ionic-angular'; import { IonicPage, ViewController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate'; import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import { import {
@ -36,10 +38,17 @@ export class AddonModAssignEditFeedbackModalPage {
@Input() plugin: AddonModAssignPlugin; // The plugin object. @Input() plugin: AddonModAssignPlugin; // The plugin object.
@Input() userId: number; // The user ID of the submission. @Input() userId: number; // The user ID of the submission.
@ViewChild('editFeedbackForm') formElement: ElementRef;
protected forceLeave = false; // To allow leaving the page without checking for changes. protected forceLeave = false; // To allow leaving the page without checking for changes.
constructor(params: NavParams, protected viewCtrl: ViewController, protected domUtils: CoreDomUtilsProvider, constructor(params: NavParams,
protected translate: TranslateService, protected feedbackDelegate: AddonModAssignFeedbackDelegate) { protected viewCtrl: ViewController,
protected domUtils: CoreDomUtilsProvider,
protected translate: TranslateService,
protected feedbackDelegate: AddonModAssignFeedbackDelegate,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) {
this.assign = params.get('assign'); this.assign = params.get('assign');
this.submission = params.get('submission'); this.submission = params.get('submission');
@ -82,6 +91,11 @@ export class AddonModAssignEditFeedbackModalPage {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
// Close the modal, sending the input data. // Close the modal, sending the input data.
this.forceLeave = true; this.forceLeave = true;
this.closeModal(this.getInputData()); this.closeModal(this.getInputData());

View File

@ -13,7 +13,7 @@
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-list> <ion-list>
<!-- @todo: plagiarism_print_disclosure --> <!-- @todo: plagiarism_print_disclosure -->
<form name="addon-mod_assign-edit-form" *ngIf="userSubmission && userSubmission.plugins && userSubmission.plugins.length"> <form name="addon-mod_assign-edit-form" *ngIf="userSubmission && userSubmission.plugins && userSubmission.plugins.length" #editSubmissionForm>
<!-- Submission statement. --> <!-- Submission statement. -->
<ion-item text-wrap *ngIf="submissionStatement"> <ion-item text-wrap *ngIf="submissionStatement">
<ion-label><core-format-text [text]="submissionStatement" [filter]="false"></core-format-text></ion-label> <ion-label><core-format-text [text]="submissionStatement" [filter]="false"></core-format-text></ion-label>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
@ -34,6 +34,9 @@ import { AddonModAssignHelperProvider } from '../../providers/helper';
templateUrl: 'edit.html', templateUrl: 'edit.html',
}) })
export class AddonModAssignEditPage implements OnInit, OnDestroy { export class AddonModAssignEditPage implements OnInit, OnDestroy {
@ViewChild('editSubmissionForm') formElement: ElementRef;
title: string; // Title to display. title: string; // Title to display.
assign: AddonModAssignAssign; // Assignment. assign: AddonModAssignAssign; // Assignment.
courseId: number; // Course ID the assignment belongs to. courseId: number; // Course ID the assignment belongs to.
@ -265,50 +268,60 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
* *
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected saveSubmission(): Promise<any> { protected async saveSubmission(): Promise<void> {
const inputData = this.getInputData(); const inputData = this.getInputData();
if (this.submissionStatement && (!inputData.submissionstatement || inputData.submissionstatement === 'false')) { if (this.submissionStatement && (!inputData.submissionstatement || inputData.submissionstatement === 'false')) {
return Promise.reject(this.translate.instant('addon.mod_assign.acceptsubmissionstatement')); throw this.translate.instant('addon.mod_assign.acceptsubmissionstatement');
} }
let modal = this.domUtils.showModalLoading(); let modal = this.domUtils.showModalLoading();
let size;
// Get size to ask for confirmation. // Get size to ask for confirmation.
return this.assignHelper.getSubmissionSizeForEdit(this.assign, this.userSubmission, inputData).catch(() => { try {
size = await this.assignHelper.getSubmissionSizeForEdit(this.assign, this.userSubmission, inputData);
} catch (error) {
// Error calculating size, return -1. // Error calculating size, return -1.
return -1; size = -1;
}).then((size) => { }
modal.dismiss(); modal.dismiss();
try {
// Confirm action. // Confirm action.
return this.fileUploaderHelper.confirmUploadFile(size, true, this.allowOffline); await this.fileUploaderHelper.confirmUploadFile(size, true, this.allowOffline);
}).then(() => {
modal = this.domUtils.showModalLoading('core.sending', true); modal = this.domUtils.showModalLoading('core.sending', true);
return this.prepareSubmissionData(inputData).then((pluginData) => { const pluginData = await this.prepareSubmissionData(inputData);
if (!Object.keys(pluginData).length) { if (!Object.keys(pluginData).length) {
// Nothing to save. // Nothing to save.
return; return;
} }
let promise; let sent: boolean;
if (this.saveOffline) { if (this.saveOffline) {
// Save submission in offline. // Save submission in offline.
promise = this.assignOfflineProvider.saveSubmission(this.assign.id, this.courseId, pluginData, sent = false;
await this.assignOfflineProvider.saveSubmission(this.assign.id, this.courseId, pluginData,
this.userSubmission.timemodified, !this.assign.submissiondrafts, this.userId); this.userSubmission.timemodified, !this.assign.submissiondrafts, this.userId);
} else { } else {
// Try to send it to server. // Try to send it to server.
promise = this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline, sent = await this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline,
this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId); this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId);
} }
return promise.then(() => {
// Clear temporary data from plugins. // Clear temporary data from plugins.
return this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, inputData); await this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, inputData);
}).then(() => {
// Submission saved, trigger event. // Submission saved, trigger events.
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: sent,
}, this.sitesProvider.getCurrentSiteId());
const params = { const params = {
assignmentId: this.assign.id, assignmentId: this.assign.id,
submissionId: this.userSubmission.id, submissionId: this.userSubmission.id,
@ -323,11 +336,9 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
this.eventsProvider.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, params, this.eventsProvider.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, params,
this.sitesProvider.getCurrentSiteId()); this.sitesProvider.getCurrentSiteId());
} }
}); } finally {
});
}).finally(() => {
modal.dismiss(); modal.dismiss();
}); }
} }
/** /**

View File

@ -21,7 +21,7 @@
<div class="addon-data-contents addon-data-entries-{{data.id}}" *ngIf="data"> <div class="addon-data-contents addon-data-entries-{{data.id}}" *ngIf="data">
<core-style [css]="data.csstemplate" prefix=".addon-data-entries-{{data.id}}"></core-style> <core-style [css]="data.csstemplate" prefix=".addon-data-entries-{{data.id}}"></core-style>
<form (ngSubmit)="save($event)" [formGroup]="editForm"> <form (ngSubmit)="save($event)" [formGroup]="editForm" #editFormEl>
<core-compile-html [text]="editFormRender" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html> <core-compile-html [text]="editFormRender" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html>
</form> </form>
</div> </div>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { Content, IonicPage, NavParams, NavController } from 'ionic-angular'; import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
@ -40,6 +40,7 @@ import { CoreTagProvider } from '@core/tag/providers/tag';
}) })
export class AddonModDataEditPage { export class AddonModDataEditPage {
@ViewChild(Content) content: Content; @ViewChild(Content) content: Content;
@ViewChild('editFormEl') formElement: ElementRef;
protected module: any; protected module: any;
protected courseId: number; protected courseId: number;
@ -216,6 +217,12 @@ export class AddonModDataEditPage {
// This is done if entry is updated when editing or creating if not. // This is done if entry is updated when editing or creating if not.
if ((this.entryId && result.updated) || (!this.entryId && result.newentryid)) { if ((this.entryId && result.updated) || (!this.entryId && result.newentryid)) {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: result.sent,
}, this.siteId);
const promises = []; const promises = [];
this.entryId = this.entryId || result.newentryid; this.entryId = this.entryId || result.newentryid;

View File

@ -13,7 +13,7 @@
<a class="tab-slide" [attr.aria-selected]="!search.searchingAdvanced" (click)="changeAdvanced(false)">{{ 'addon.mod_data.search' | translate}}</a> <a class="tab-slide" [attr.aria-selected]="!search.searchingAdvanced" (click)="changeAdvanced(false)">{{ 'addon.mod_data.search' | translate}}</a>
<a class="tab-slide" [attr.aria-selected]="search.searchingAdvanced" (click)="changeAdvanced(true)">{{ 'addon.mod_data.advancedsearch' | translate }}</a> <a class="tab-slide" [attr.aria-selected]="search.searchingAdvanced" (click)="changeAdvanced(true)">{{ 'addon.mod_data.advancedsearch' | translate }}</a>
</div> </div>
<form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm"> <form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm" #searchFormEl>
<ion-list no-margin> <ion-list no-margin>
<ion-item [hidden]="search.searchingAdvanced"> <ion-item [hidden]="search.searchingAdvanced">
<ion-input type="text" placeholder="{{ 'addon.mod_data.search' | translate}}" [(ngModel)]="search.text" name="text" formControlName="text"></ion-input> <ion-input type="text" placeholder="{{ 'addon.mod_data.search' | translate}}" [(ngModel)]="search.text" name="text" formControlName="text"></ion-input>

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavParams, ViewController } from 'ionic-angular'; import { IonicPage, NavParams, ViewController } from 'ionic-angular';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -32,6 +34,8 @@ import { CoreTagProvider } from '@core/tag/providers/tag';
templateUrl: 'search.html', templateUrl: 'search.html',
}) })
export class AddonModDataSearchPage { export class AddonModDataSearchPage {
@ViewChild('searchFormEl') formElement: ElementRef;
search: any; search: any;
fields: any; fields: any;
data: any; data: any;
@ -41,10 +45,17 @@ export class AddonModDataSearchPage {
jsData: any; jsData: any;
fieldsArray: any; fieldsArray: any;
constructor(params: NavParams, private viewCtrl: ViewController, fb: FormBuilder, protected utils: CoreUtilsProvider, constructor(params: NavParams,
protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate, protected viewCtrl: ViewController,
protected textUtils: CoreTextUtilsProvider, protected dataHelper: AddonModDataHelperProvider, fb: FormBuilder,
private tagProvider: CoreTagProvider) { protected utils: CoreUtilsProvider,
protected domUtils: CoreDomUtilsProvider,
protected fieldsDelegate: AddonModDataFieldsDelegate,
protected textUtils: CoreTextUtilsProvider,
protected dataHelper: AddonModDataHelperProvider,
protected tagProvider: CoreTagProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) {
this.search = params.get('search'); this.search = params.get('search');
this.fields = params.get('fields'); this.fields = params.get('fields');
this.data = params.get('data'); this.data = params.get('data');
@ -209,6 +220,11 @@ export class AddonModDataSearchPage {
this.search.sortBy = searchedData.sortBy; this.search.sortBy = searchedData.sortBy;
this.search.sortDirection = searchedData.sortDirection; this.search.sortDirection = searchedData.sortDirection;
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
this.closeModal(this.search); this.closeModal(this.search);
} }
} }

View File

@ -126,7 +126,8 @@ export class AddonModDataProvider {
.then((entry) => { .then((entry) => {
return { return {
// Return provissional entry Id. // Return provissional entry Id.
newentryid: entry newentryid: entry,
sent: false,
}; };
}); });
}; };
@ -142,7 +143,11 @@ export class AddonModDataProvider {
return storeOffline(); return storeOffline();
} }
return this.addEntryOnline(dataId, contents, groupId, siteId).catch((error) => { return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => {
result.sent = true;
return result;
}).catch((error) => {
if (this.utils.isWebServiceError(error)) { if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted. // The WebService has thrown an error, this means that responses cannot be submitted.
return Promise.reject(error); return Promise.reject(error);
@ -194,7 +199,12 @@ export class AddonModDataProvider {
const storeOffline = (): Promise<any> => { const storeOffline = (): Promise<any> => {
const action = approve ? 'approve' : 'disapprove'; const action = approve ? 'approve' : 'disapprove';
return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId); return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId)
.then(() => {
return {
sent: false,
};
});
}; };
// Get if the opposite action is not synced. // Get if the opposite action is not synced.
@ -210,7 +220,11 @@ export class AddonModDataProvider {
return storeOffline(); return storeOffline();
} }
return this.approveEntryOnline(entryId, approve, siteId).catch((error) => { return this.approveEntryOnline(entryId, approve, siteId).then(() => {
return {
sent: true,
};
}).catch((error) => {
if (this.utils.isWebServiceError(error)) { if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted. // The WebService has thrown an error, this means that responses cannot be submitted.
return Promise.reject(error); return Promise.reject(error);
@ -288,7 +302,12 @@ export class AddonModDataProvider {
// Convenience function to store a data to be synchronized later. // Convenience function to store a data to be synchronized later.
const storeOffline = (): Promise<any> => { const storeOffline = (): Promise<any> => {
return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId); return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId)
.then(() => {
return {
sent: false,
};
});
}; };
let justAdded = false; let justAdded = false;
@ -318,7 +337,11 @@ export class AddonModDataProvider {
return storeOffline(); return storeOffline();
} }
return this.deleteEntryOnline(entryId, siteId).catch((error) => { return this.deleteEntryOnline(entryId, siteId).then(() => {
return {
sent: true,
};
}).catch((error) => {
if (this.utils.isWebServiceError(error)) { if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted. // The WebService has thrown an error, this means that responses cannot be submitted.
return Promise.reject(error); return Promise.reject(error);
@ -368,7 +391,8 @@ export class AddonModDataProvider {
return this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId) return this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId)
.then(() => { .then(() => {
return { return {
updated: true updated: true,
sent: false,
}; };
}); });
}; };
@ -408,6 +432,7 @@ export class AddonModDataProvider {
return this.addEntry(dataId, entryId, courseId, contents, groupId, fields, siteId, forceOffline) return this.addEntry(dataId, entryId, courseId, contents, groupId, fields, siteId, forceOffline)
.then((result) => { .then((result) => {
result.updated = true; result.updated = true;
result.sent = true;
return result; return result;
}); });
@ -418,7 +443,11 @@ export class AddonModDataProvider {
return storeOffline(); return storeOffline();
} }
return this.editEntryOnline(entryId, contents, siteId).catch((error) => { return this.editEntryOnline(entryId, contents, siteId).then((result) => {
result.sent = true;
return result;
}).catch((error) => {
if (this.utils.isWebServiceError(error)) { if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted. // The WebService has thrown an error, this means that responses cannot be submitted.
return Promise.reject(error); return Promise.reject(error);

View File

@ -34,7 +34,7 @@
<!-- Input password for protected lessons. --> <!-- Input password for protected lessons. -->
<ion-card *ngIf="askPassword"> <ion-card *ngIf="askPassword">
<form ion-list (ngSubmit)="submitPassword($event, passwordinput)"> <form ion-list (ngSubmit)="submitPassword($event, passwordinput)" #passwordForm>
<ion-item text-wrap> <ion-item text-wrap>
<ion-label stacked>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label> <ion-label stacked>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
<core-show-password item-content [name]="'password'"> <core-show-password item-content [name]="'password'">

View File

@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Optional, Injector, Input, ViewChild } from '@angular/core'; import { Component, Optional, Injector, Input, ViewChild, ElementRef } from '@angular/core';
import { Content, NavController } from 'ionic-angular'; import { Content, NavController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
@ -35,6 +36,7 @@ import { CoreTabsComponent } from '@components/tabs/tabs';
}) })
export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityComponent { export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityComponent {
@ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent; @ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent;
@ViewChild('passwordForm') formElement: ElementRef;
@Input() group: number; // The group to display. @Input() group: number; // The group to display.
@Input() action: string; // The "action" to display first. @Input() action: string; // The "action" to display first.
@ -584,6 +586,11 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
this.loaded = true; this.loaded = true;
this.refreshIcon = 'refresh'; this.refreshIcon = 'refresh';
this.syncIcon = 'sync'; this.syncIcon = 'sync';
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
}, this.siteId);
}); });
} }

View File

@ -9,7 +9,7 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content padding class="addon-mod_lesson-password-modal"> <ion-content padding class="addon-mod_lesson-password-modal">
<form ion-list (ngSubmit)="submitPassword($event, passwordinput)"> <form ion-list (ngSubmit)="submitPassword($event, passwordinput)" #passwordForm>
<ion-item> <ion-item>
<core-show-password item-content [name]="'password'"> <core-show-password item-content [name]="'password'">
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label> <ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>

View File

@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, ViewController } from 'ionic-angular'; import { IonicPage, ViewController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
/** /**
* Modal that asks the password for a lesson. * Modal that asks the password for a lesson.
@ -24,8 +26,11 @@ import { IonicPage, ViewController } from 'ionic-angular';
templateUrl: 'password-modal.html', templateUrl: 'password-modal.html',
}) })
export class AddonModLessonPasswordModalPage { export class AddonModLessonPasswordModalPage {
@ViewChild('passwordForm') formElement: ElementRef;
constructor(protected viewCtrl: ViewController) { } constructor(protected viewCtrl: ViewController,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) { }
/** /**
* Send the password back. * Send the password back.
@ -37,6 +42,11 @@ export class AddonModLessonPasswordModalPage {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
this.viewCtrl.dismiss(password.value); this.viewCtrl.dismiss(password.value);
} }

View File

@ -38,7 +38,7 @@
<!-- Question page. --> <!-- Question page. -->
<!-- We need to set ngIf loaded to make formGroup directive restart every time a page changes, see MOBILE-2540. --> <!-- We need to set ngIf loaded to make formGroup directive restart every time a page changes, see MOBILE-2540. -->
<form *ngIf="question && loaded" ion-list [formGroup]="questionForm"> <form *ngIf="question && loaded" ion-list [formGroup]="questionForm" #questionFormEl>
<ion-item-divider text-wrap *ngIf="pageContent"> <ion-item-divider text-wrap *ngIf="pageContent">
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="pageContent" contextLevel="module" [contextInstanceId]="lesson.coursemodule" [courseId]="courseId"></core-format-text> <core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="pageContent" contextLevel="module" [contextInstanceId]="lesson.coursemodule" [courseId]="courseId"></core-format-text>
</ion-item-divider> </ion-item-divider>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ElementRef } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular'; import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -41,6 +41,7 @@ import { AddonModLessonHelperProvider } from '../../providers/helper';
}) })
export class AddonModLessonPlayerPage implements OnInit, OnDestroy { export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
@ViewChild(Content) content: Content; @ViewChild(Content) content: Content;
@ViewChild('questionFormEl') formElement: ElementRef;
component = AddonModLessonProvider.COMPONENT; component = AddonModLessonProvider.COMPONENT;
LESSON_EOL = AddonModLessonProvider.LESSON_EOL; LESSON_EOL = AddonModLessonProvider.LESSON_EOL;
@ -540,15 +541,23 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
* Process a page, sending some data. * Process a page, sending some data.
* *
* @param data The data to send. * @param data The data to send.
* @param formSubmitted Whether a form was submitted.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected processPage(data: any): Promise<any> { protected processPage(data: any, formSubmitted?: boolean): Promise<any> {
this.loaded = false; this.loaded = false;
const args = [this.lesson, this.courseId, this.pageData, data, this.password, this.review, this.offline, this.accessInfo, const args = [this.lesson, this.courseId, this.pageData, data, this.password, this.review, this.offline, this.accessInfo,
this.jumps]; this.jumps];
return this.callFunction(this.lessonProvider.processPage.bind(this.lessonProvider), args, 6, 8).then((result) => { return this.callFunction(this.lessonProvider.processPage.bind(this.lessonProvider), args, 6, 8).then((result) => {
if (formSubmitted) {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: result.sent,
}, this.sitesProvider.getCurrentSiteId());
}
if (!this.offline && !this.review && this.lessonProvider.isLessonOffline(this.lesson)) { if (!this.offline && !this.review && this.lessonProvider.isLessonOffline(this.lesson)) {
// Lesson allows offline and the user changed some data in server. Update cached data. // Lesson allows offline and the user changed some data in server. Update cached data.
const retake = this.accessInfo.attemptscount; const retake = this.accessInfo.attemptscount;
@ -637,7 +646,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
// Use getRawValue to include disabled values. // Use getRawValue to include disabled values.
const data = this.lessonHelper.prepareQuestionData(this.question, this.questionForm.getRawValue()); const data = this.lessonHelper.prepareQuestionData(this.question, this.questionForm.getRawValue());
this.processPage(data).finally(() => { this.processPage(data, true).finally(() => {
this.loaded = true; this.loaded = true;
}); });
} }

View File

@ -3089,6 +3089,7 @@ export class AddonModLessonProvider {
result.warnings = []; result.warnings = [];
result.displaymenu = pageData.displaymenu; // Keep the same value since we can't calculate it in offline. result.displaymenu = pageData.displaymenu; // Keep the same value since we can't calculate it in offline.
result.messages = this.getPageProcessMessages(lesson, accessInfo, result, review, jumps); result.messages = this.getPageProcessMessages(lesson, accessInfo, result, review, jumps);
result.sent = false;
Object.assign(result, calculatedData); Object.assign(result, calculatedData);
return result; return result;
@ -3104,6 +3105,8 @@ export class AddonModLessonProvider {
review: review review: review
}, this.sitesProvider.getCurrentSiteId()); }, this.sitesProvider.getCurrentSiteId());
response.sent = true;
return response; return response;
}); });
} }

View File

@ -45,7 +45,7 @@
</div> </div>
<!-- Questions --> <!-- Questions -->
<form name="addon-mod_quiz-player-form" *ngIf="questions && questions.length && !quizAborted && !showSummary"> <form name="addon-mod_quiz-player-form" *ngIf="questions && questions.length && !quizAborted && !showSummary" #quizForm>
<div *ngFor="let question of questions"> <div *ngFor="let question of questions">
<ion-card id="addon-mod_quiz-question-{{question.slot}}"> <ion-card id="addon-mod_quiz-question-{{question.slot}}">
<!-- "Header" of the question. --> <!-- "Header" of the question. -->

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ViewChildren, QueryList } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ViewChildren, QueryList, ElementRef } from '@angular/core';
import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular'; import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
@ -41,6 +41,7 @@ import { Subscription } from 'rxjs';
export class AddonModQuizPlayerPage implements OnInit, OnDestroy { export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
@ViewChild(Content) content: Content; @ViewChild(Content) content: Content;
@ViewChildren(CoreQuestionComponent) questionComponents: QueryList<CoreQuestionComponent>; @ViewChildren(CoreQuestionComponent) questionComponents: QueryList<CoreQuestionComponent>;
@ViewChild('quizForm') formElement: ElementRef;
quiz: any; // The quiz the attempt belongs to. quiz: any; // The quiz the attempt belongs to.
attempt: any; // The attempt being attempted. attempt: any; // The attempt being attempted.
@ -585,6 +586,11 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
// Answers saved, cancel auto save. // Answers saved, cancel auto save.
this.autoSave.cancelAutoSave(); this.autoSave.cancelAutoSave();
this.autoSave.hideAutoSaveError(); this.autoSave.hideAutoSaveError();
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: !this.offline,
}, this.sitesProvider.getCurrentSiteId());
}); });
} }

View File

@ -10,7 +10,7 @@
</ion-header> </ion-header>
<ion-content padding class="addon-mod_quiz-preflight-modal"> <ion-content padding class="addon-mod_quiz-preflight-modal">
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<form ion-list [formGroup]="preflightForm" (ngSubmit)="sendData($event)"> <form ion-list [formGroup]="preflightForm" (ngSubmit)="sendData($event)" #preflightFormEl>
<!-- Access rules. --> <!-- Access rules. -->
<ng-container *ngFor="let data of accessRulesData; let last = last"> <ng-container *ngFor="let data of accessRulesData; let last = last">
<core-dynamic-component [component]="data.component" [data]="data.data"> <core-dynamic-component [component]="data.component" [data]="data.data">

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, Injector, ViewChild } from '@angular/core'; import { Component, OnInit, Injector, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, ViewController, NavParams, Content } from 'ionic-angular'; import { IonicPage, ViewController, NavParams, Content } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-delegate'; import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-delegate';
@ -31,6 +32,7 @@ import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-del
export class AddonModQuizPreflightModalPage implements OnInit { export class AddonModQuizPreflightModalPage implements OnInit {
@ViewChild(Content) content: Content; @ViewChild(Content) content: Content;
@ViewChild('preflightFormEl') formElement: ElementRef;
preflightForm: FormGroup; preflightForm: FormGroup;
title: string; title: string;
@ -43,9 +45,15 @@ export class AddonModQuizPreflightModalPage implements OnInit {
protected siteId: string; protected siteId: string;
protected rules: string[]; protected rules: string[];
constructor(params: NavParams, fb: FormBuilder, translate: TranslateService, sitesProvider: CoreSitesProvider, constructor(params: NavParams,
protected viewCtrl: ViewController, protected accessRuleDelegate: AddonModQuizAccessRuleDelegate, fb: FormBuilder,
protected injector: Injector, protected domUtils: CoreDomUtilsProvider) { translate: TranslateService,
sitesProvider: CoreSitesProvider,
protected viewCtrl: ViewController,
protected accessRuleDelegate: AddonModQuizAccessRuleDelegate,
protected injector: Injector,
protected domUtils: CoreDomUtilsProvider,
protected eventsProvider: CoreEventsProvider) {
this.title = params.get('title') || translate.instant('addon.mod_quiz.startattempt'); this.title = params.get('title') || translate.instant('addon.mod_quiz.startattempt');
this.quiz = params.get('quiz'); this.quiz = params.get('quiz');
@ -112,6 +120,11 @@ export class AddonModQuizPreflightModalPage implements OnInit {
this.domUtils.showErrorModal('core.errorinvalidform', true); this.domUtils.showErrorModal('core.errorinvalidform', true);
} }
} else { } else {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.siteId);
this.viewCtrl.dismiss(this.preflightForm.value); this.viewCtrl.dismiss(this.preflightForm.value);
} }
} }

View File

@ -11,7 +11,7 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<form ion-list [formGroup]="pageForm"> <form ion-list [formGroup]="pageForm" #editPageForm>
<ion-item text-wrap *ngIf="canEditTitle" class="item-title"> <ion-item text-wrap *ngIf="canEditTitle" class="item-title">
<ion-input name="title" type="text" [placeholder]="'addon.mod_wiki.newpagetitle' | translate" [formControlName]="'title'"></ion-input> <ion-input name="title" type="text" [placeholder]="'addon.mod_wiki.newpagetitle' | translate" [formControlName]="'title'"></ion-input>
</ion-item> </ion-item>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { FormControl, FormGroup, FormBuilder } from '@angular/forms'; import { FormControl, FormGroup, FormBuilder } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -37,6 +37,8 @@ import { AddonModWikiSyncProvider, AddonModWikiSyncSubwikiResult } from '../../p
}) })
export class AddonModWikiEditPage implements OnInit, OnDestroy { export class AddonModWikiEditPage implements OnInit, OnDestroy {
@ViewChild('editPageForm') formElement: ElementRef;
title: string; // Title to display. title: string; // Title to display.
pageForm: FormGroup; // The form group. pageForm: FormGroup; // The form group.
contentControl: FormControl; // The FormControl for the page content. contentControl: FormControl; // The FormControl for the page content.
@ -423,6 +425,12 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
if (this.editing) { if (this.editing) {
// Edit existing page. // Edit existing page.
promise = this.wikiProvider.editPage(this.pageId, text, this.section).then(() => { promise = this.wikiProvider.editPage(this.pageId, text, this.section).then(() => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
}, this.sitesProvider.getCurrentSiteId());
// Invalidate page since it changed. // Invalidate page since it changed.
return this.wikiProvider.invalidatePage(this.pageId).then(() => { return this.wikiProvider.invalidatePage(this.pageId).then(() => {
return this.gotoPage(title); return this.gotoPage(title);
@ -456,6 +464,12 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
let wikiId = this.wikiId || (this.module && this.module.instance); let wikiId = this.wikiId || (this.module && this.module.instance);
return this.wikiProvider.newPage(title, text, this.subwikiId, wikiId, this.userId, this.groupId).then((id) => { return this.wikiProvider.newPage(title, text, this.subwikiId, wikiId, this.userId, this.groupId).then((id) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: id > 0,
}, this.sitesProvider.getCurrentSiteId());
if (id > 0) { if (id > 0) {
// Page was created, get its data and go to the page. // Page was created, get its data and go to the page.
this.pageId = id; this.pageId = id;

View File

@ -1,6 +1,6 @@
<h3 padding>{{ 'addon.mod_workshop.assessmentform' | translate }}</h3> <h3 padding>{{ 'addon.mod_workshop.assessmentform' | translate }}</h3>
<form name="mma-mod_workshop-assessment-form"> <form name="mma-mod_workshop-assessment-form" #assessmentForm>
<core-loading [hideUntil]="assessmentStrategyLoaded"> <core-loading [hideUntil]="assessmentStrategyLoaded">
<ng-container *ngIf="componentClass && assessmentStrategyLoaded"> <ng-container *ngIf="componentClass && assessmentStrategyLoaded">
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component> <core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, OnInit, Injector } from '@angular/core'; import { Component, Input, OnInit, Injector, ViewChild, ElementRef } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreSyncProvider } from '@providers/sync'; import { CoreSyncProvider } from '@providers/sync';
@ -44,6 +44,8 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit {
@Input() strategy: string; @Input() strategy: string;
@Input() edit?: boolean; @Input() edit?: boolean;
@ViewChild('assessmentForm') formElement: ElementRef;
componentClass: any; componentClass: any;
data = { data = {
workshopId: 0, workshopId: 0,
@ -292,7 +294,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit {
// Save assessment in offline. // Save assessment in offline.
return this.workshopOffline.saveAssessment(this.workshop.id, this.assessmentId, this.workshop.course, return this.workshopOffline.saveAssessment(this.workshop.id, this.assessmentId, this.workshop.course,
assessmentData).then(() => { assessmentData).then(() => {
// Don't return anything. return false;
}); });
} }
@ -301,6 +303,12 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit {
return this.workshopProvider.updateAssessment(this.workshop.id, this.assessmentId, this.workshop.course, return this.workshopProvider.updateAssessment(this.workshop.id, this.assessmentId, this.workshop.course,
assessmentData, false, allowOffline); assessmentData, false, allowOffline);
}).then((grade) => { }).then((grade) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: !!grade,
}, this.sitesProvider.getCurrentSiteId());
const promises = []; const promises = [];
// If sent to the server, invalidate and clean. // If sent to the server, invalidate and clean.

View File

@ -38,7 +38,7 @@
<addon-mod-workshop-assessment-strategy *ngIf="assessment && assessmentId && showGrade(assessment.grade) && workshop && access" [workshop]="workshop" [access]="access" [assessmentId]="assessmentId" [userId]="profile && profile.id" [strategy]="strategy"></addon-mod-workshop-assessment-strategy> <addon-mod-workshop-assessment-strategy *ngIf="assessment && assessmentId && showGrade(assessment.grade) && workshop && access" [workshop]="workshop" [access]="access" [assessmentId]="assessmentId" [userId]="profile && profile.id" [strategy]="strategy"></addon-mod-workshop-assessment-strategy>
<form ion-list [formGroup]="evaluateForm" *ngIf="evaluating"> <form ion-list [formGroup]="evaluateForm" *ngIf="evaluating" #evaluateFormEl>
<ion-item text-wrap> <ion-item text-wrap>
<h2>{{ 'addon.mod_workshop.assessmentsettings' | translate }}</h2> <h2>{{ 'addon.mod_workshop.assessmentsettings' | translate }}</h2>
</ion-item> </ion-item>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavParams, NavController } from 'ionic-angular'; import { IonicPage, NavParams, NavController } from 'ionic-angular';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -39,6 +39,8 @@ import { AddonModWorkshopSyncProvider } from '../../providers/sync';
}) })
export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy {
@ViewChild('evaluateFormEl') formElement: ElementRef;
assessment: any; assessment: any;
submission: any; submission: any;
profile: any; profile: any;
@ -340,7 +342,13 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy {
// Try to send it to server. // Try to send it to server.
return this.workshopProvider.evaluateAssessment(this.workshopId, this.assessmentId, this.courseId, inputData.text, return this.workshopProvider.evaluateAssessment(this.workshopId, this.assessmentId, this.courseId, inputData.text,
inputData.weight, inputData.grade).then(() => { inputData.weight, inputData.grade).then((result) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: !!result,
}, this.siteId);
const data = { const data = {
workshopId: this.workshopId, workshopId: this.workshopId,
assessmentId: this.assessmentId, assessmentId: this.assessmentId,

View File

@ -10,7 +10,7 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<form ion-list [formGroup]="editForm" *ngIf="workshop"> <form ion-list [formGroup]="editForm" *ngIf="workshop" #editFormEl>
<ion-item text-wrap> <ion-item text-wrap>
<ion-label stacked core-mark-required="true">{{ 'addon.mod_workshop.submissiontitle' | translate }}</ion-label> <ion-label stacked core-mark-required="true">{{ 'addon.mod_workshop.submissiontitle' | translate }}</ion-label>
<ion-input name="title" type="text" [placeholder]="'addon.mod_workshop.submissiontitle' | translate" formControlName="title"></ion-input> <ion-input name="title" type="text" [placeholder]="'addon.mod_workshop.submissiontitle' | translate" formControlName="title"></ion-input>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavParams, NavController } from 'ionic-angular'; import { IonicPage, NavParams, NavController } from 'ionic-angular';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -37,6 +37,8 @@ import { AddonModWorkshopOfflineProvider } from '../../providers/offline';
}) })
export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
@ViewChild('editFormEl') formElement: ElementRef;
module: any; module: any;
courseId: number; courseId: number;
access: any; access: any;
@ -352,7 +354,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
// Save submission in offline. // Save submission in offline.
return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title, return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title,
inputData.content, attachmentsId, submissionId, 'update').then(() => { inputData.content, attachmentsId, submissionId, 'update').then(() => {
// Don't return anything. return false;
}); });
} }
@ -366,7 +368,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
// Save submission in offline. // Save submission in offline.
return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title, inputData.content, return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title, inputData.content,
attachmentsId, submissionId, 'add').then(() => { attachmentsId, submissionId, 'add').then(() => {
// Don't return anything. return false;
}); });
} }
@ -375,6 +377,12 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
return this.workshopProvider.addSubmission(this.workshopId, this.courseId, inputData.title, inputData.content, return this.workshopProvider.addSubmission(this.workshopId, this.courseId, inputData.title, inputData.content,
attachmentsId, undefined, submissionId, allowOffline); attachmentsId, undefined, submissionId, allowOffline);
}).then((newSubmissionId) => { }).then((newSubmissionId) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: !!newSubmissionId,
}, this.siteId);
const data = { const data = {
workshopId: this.workshopId, workshopId: this.workshopId,
cmId: this.module.cmid cmId: this.module.cmid

View File

@ -65,7 +65,7 @@
<addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer" [courseId]="courseId" summary="true" [workshop]="workshop" [access]="access"></addon-mod-workshop-assessment> <addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer" [courseId]="courseId" summary="true" [workshop]="workshop" [access]="access"></addon-mod-workshop-assessment>
</ion-list> </ion-list>
<form ion-list [formGroup]="feedbackForm" *ngIf="canAddFeedback"> <form ion-list [formGroup]="feedbackForm" *ngIf="canAddFeedback" #feedbackFormEl>
<ion-item text-wrap> <ion-item text-wrap>
<h2>{{ 'addon.mod_workshop.feedbackauthor' | translate }}</h2> <h2>{{ 'addon.mod_workshop.feedbackauthor' | translate }}</h2>
</ion-item> </ion-item>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core';
import { Content, IonicPage, NavParams, NavController } from 'ionic-angular'; import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
import { FormGroup, FormBuilder } from '@angular/forms'; import { FormGroup, FormBuilder } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -41,6 +41,7 @@ import { AddonModWorkshopSyncProvider } from '../../providers/sync';
export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy {
@ViewChild(AddonModWorkshopAssessmentStrategyComponent) assessmentStrategy: AddonModWorkshopAssessmentStrategyComponent; @ViewChild(AddonModWorkshopAssessmentStrategyComponent) assessmentStrategy: AddonModWorkshopAssessmentStrategyComponent;
@ViewChild('feedbackFormEl') formElement: ElementRef;
module: any; module: any;
workshop: any; workshop: any;
@ -444,7 +445,13 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy {
// Try to send it to server. // Try to send it to server.
return this.workshopProvider.evaluateSubmission(this.workshopId, this.submissionId, this.courseId, inputData.text, return this.workshopProvider.evaluateSubmission(this.workshopId, this.submissionId, this.courseId, inputData.text,
inputData.published, inputData.grade).then(() => { inputData.published, inputData.grade).then((result) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: !!result,
}, this.siteId);
const data = { const data = {
workshopId: this.workshopId, workshopId: this.workshopId,
cmId: this.module.cmid, cmId: this.module.cmid,

View File

@ -9,7 +9,7 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<form name="itemEdit" (ngSubmit)="addNote($event)"> <form name="itemEdit" (ngSubmit)="addNote($event)" #itemEdit>
<ion-item> <ion-item>
<ion-label>{{ 'addon.notes.publishstate' | translate }}</ion-label> <ion-label>{{ 'addon.notes.publishstate' | translate }}</ion-label>
<ion-select [(ngModel)]="type" name="publishState" interface="popover"> <ion-select [(ngModel)]="type" name="publishState" interface="popover">

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, ViewController, NavParams } from 'ionic-angular'; import { IonicPage, ViewController, NavParams } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonNotesProvider } from '../../providers/notes'; import { AddonNotesProvider } from '../../providers/notes';
@ -27,14 +29,22 @@ import { AddonNotesProvider } from '../../providers/notes';
templateUrl: 'add.html', templateUrl: 'add.html',
}) })
export class AddonNotesAddPage { export class AddonNotesAddPage {
@ViewChild('itemEdit') formElement: ElementRef;
userId: number; userId: number;
courseId: number; courseId: number;
type = 'personal'; type = 'personal';
text = ''; text = '';
processing = false; processing = false;
constructor(params: NavParams, private viewCtrl: ViewController, private appProvider: CoreAppProvider, constructor(params: NavParams,
private domUtils: CoreDomUtilsProvider, private notesProvider: AddonNotesProvider) { protected viewCtrl: ViewController,
protected appProvider: CoreAppProvider,
protected domUtils: CoreDomUtilsProvider,
protected notesProvider: AddonNotesProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) {
this.userId = params.get('userId'); this.userId = params.get('userId');
this.courseId = params.get('courseId'); this.courseId = params.get('courseId');
this.type = params.get('type') || 'personal'; this.type = params.get('type') || 'personal';
@ -54,6 +64,12 @@ export class AddonNotesAddPage {
// Freeze the add note button. // Freeze the add note button.
this.processing = true; this.processing = true;
this.notesProvider.addNote(this.userId, this.courseId, this.type, this.text).then((sent) => { this.notesProvider.addNote(this.userId, this.courseId, this.type, this.text).then((sent) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: sent,
}, this.sitesProvider.getCurrentSiteId());
this.viewCtrl.dismiss({type: this.type, sent: true}).finally(() => { this.viewCtrl.dismiss({type: this.type, sent: true}).finally(() => {
this.domUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, 3000); this.domUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, 3000);
}); });

View File

@ -1,4 +1,4 @@
<form (ngSubmit)="changeName(newFileName, $event)"> <form (ngSubmit)="changeName(newFileName, $event)" #nameForm>
<a ion-item text-wrap stacked class="item-media" [class.item-input]="editMode" (click)="fileClicked($event)" detail-none> <a ion-item text-wrap stacked class="item-media" [class.item-input]="editMode" (click)="fileClicked($event)" detail-none>
<img [src]="fileIcon" alt="{{fileExtension}}" role="presentation" item-start /> <img [src]="fileIcon" alt="{{fileExtension}}" role="presentation" item-start />

View File

@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; import { Component, Input, Output, OnInit, EventEmitter, ViewChild, ElementRef } from '@angular/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreFileProvider } from '@providers/file'; import { CoreFileProvider } from '@providers/file';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -38,6 +40,8 @@ export class CoreLocalFileComponent implements OnInit {
@Output() onRename?: EventEmitter<any>; // Will notify when the file is renamed. Receives the FileEntry as the param. @Output() onRename?: EventEmitter<any>; // Will notify when the file is renamed. Receives the FileEntry as the param.
@Output() onClick?: EventEmitter<void>; // Will notify when the file is clicked. Only if overrideClick is true. @Output() onClick?: EventEmitter<void>; // Will notify when the file is clicked. Only if overrideClick is true.
@ViewChild('nameForm') formElement: ElementRef;
fileName: string; fileName: string;
fileIcon: string; fileIcon: string;
fileExtension: string; fileExtension: string;
@ -47,12 +51,14 @@ export class CoreLocalFileComponent implements OnInit {
editMode: boolean; editMode: boolean;
relativePath: string; relativePath: string;
constructor(private mimeUtils: CoreMimetypeUtilsProvider, constructor(protected mimeUtils: CoreMimetypeUtilsProvider,
private utils: CoreUtilsProvider, protected utils: CoreUtilsProvider,
private textUtils: CoreTextUtilsProvider, protected textUtils: CoreTextUtilsProvider,
private fileProvider: CoreFileProvider, protected fileProvider: CoreFileProvider,
private domUtils: CoreDomUtilsProvider, protected domUtils: CoreDomUtilsProvider,
private timeUtils: CoreTimeUtilsProvider) { protected timeUtils: CoreTimeUtilsProvider,
protected sitesProvider: CoreSitesProvider,
protected eventsProvider: CoreEventsProvider) {
this.onDelete = new EventEmitter(); this.onDelete = new EventEmitter();
this.onRename = new EventEmitter(); this.onRename = new EventEmitter();
this.onClick = new EventEmitter(); this.onClick = new EventEmitter();
@ -152,6 +158,12 @@ export class CoreLocalFileComponent implements OnInit {
}).catch(() => { }).catch(() => {
// File doesn't exist, move it. // File doesn't exist, move it.
return this.fileProvider.moveFile(this.relativePath, newPath).then((fileEntry) => { return this.fileProvider.moveFile(this.relativePath, newPath).then((fileEntry) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
this.editMode = false; this.editMode = false;
this.file = fileEntry; this.file = fileEntry;
this.loadFileBasicData(); this.loadFileBasicData();

View File

@ -0,0 +1,100 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
/**
* Component to display a "search box".
*
* @description
* This component will display a standalone search box with its search button in order to have a better UX.
*
* Example usage:
* <core-search-box (onSubmit)="search($event)" [placeholder]="'core.courses.search' | translate"
* [searchLabel]="'core.courses.search' | translate" autoFocus="true"></core-search-box>
*/
@Component({
selector: 'core-search-box',
templateUrl: 'core-search-box.html'
})
export class CoreSearchBoxComponent implements OnInit {
@Input() searchLabel?: string; // Label to be used on action button.
@Input() placeholder?: string; // Placeholder text for search text input.
@Input() autocorrect = 'on'; // Enables/disable Autocorrection on search text input.
@Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input.
@Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view.
@Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted.
@Input() showClear = true; // Show/hide clear button.
@Input() disabled = false; // Disables the input text.
@Input() initialSearch: string; // Initial search text.
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
@Output() onClear: EventEmitter<void>; // Send event when clearing the search form.
@ViewChild('searchForm') formElement: ElementRef;
searched = false;
searchText = '';
constructor(protected translate: TranslateService,
protected utils: CoreUtilsProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) {
this.onSubmit = new EventEmitter<string>();
this.onClear = new EventEmitter<void>();
}
ngOnInit(): void {
this.searchLabel = this.searchLabel || this.translate.instant('core.search');
this.placeholder = this.placeholder || this.translate.instant('core.search');
this.spellcheck = this.utils.isTrueOrOne(this.spellcheck);
this.showClear = this.utils.isTrueOrOne(this.showClear);
this.searchText = this.initialSearch || '';
}
/**
* Form submitted.
*
* @param e Event.
*/
submitForm(e: Event): void {
e.preventDefault();
e.stopPropagation();
if (this.searchText.length < this.lengthCheck) {
// The view should handle this case, but we check it here too just in case.
return;
}
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
this.searched = true;
this.onSubmit.emit(this.searchText);
}
/**
* Form submitted.
*/
clearForm(): void {
this.searched = false;
this.searchText = '';
this.onClear.emit();
}
}

View File

@ -1,4 +1,4 @@
<form> <form #messageForm>
<textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keydown.enter)="enterClicked($event)" (keydown.control.enter)="enterClicked($event, 'control')" (keydown.meta.enter)="enterClicked($event, 'meta')" aria-multiline="true"></textarea> <textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keydown.enter)="enterClicked($event)" (keydown.control.enter)="enterClicked($event, 'control')" (keydown.meta.enter)="enterClicked($event, 'meta')" aria-multiline="true"></textarea>
<ion-buttons end> <ion-buttons end>
<button ion-button icon-only clear="true" type="submit" [disabled]="!message || sendDisabled" [attr.aria-label]="'core.send' | translate" [core-suppress-events] (onClick)="submitForm($event)"> <button ion-button icon-only clear="true" type="submit" [disabled]="!message || sendDisabled" [attr.aria-label]="'core.send' | translate" [core-suppress-events] (onClick)="submitForm($event)">

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreConfigProvider } from '@providers/config'; import { CoreConfigProvider } from '@providers/config';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
@ -43,10 +43,16 @@ export class CoreSendMessageFormComponent implements OnInit {
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the message form. @Output() onSubmit: EventEmitter<string>; // Send data when submitting the message form.
@Output() onResize: EventEmitter<void>; // Emit when resizing the textarea. @Output() onResize: EventEmitter<void>; // Emit when resizing the textarea.
@ViewChild('messageForm') formElement: ElementRef;
protected sendOnEnter: boolean; protected sendOnEnter: boolean;
constructor(private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, configProvider: CoreConfigProvider, constructor(protected utils: CoreUtilsProvider,
eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider) { protected textUtils: CoreTextUtilsProvider,
configProvider: CoreConfigProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider,
protected appProvider: CoreAppProvider) {
this.onSubmit = new EventEmitter(); this.onSubmit = new EventEmitter();
this.onResize = new EventEmitter(); this.onResize = new EventEmitter();
@ -82,6 +88,11 @@ export class CoreSendMessageFormComponent implements OnInit {
this.message = ''; // Reset the form. this.message = ''; // Reset the form.
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
value = this.textUtils.replaceNewLines(value, '<br>'); value = this.textUtils.replaceNewLines(value, '<br>');
this.onSubmit.emit(value); this.onSubmit.emit(value);
} }

View File

@ -9,7 +9,7 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<form name="itemEdit" (ngSubmit)="addComment($event)"> <form name="itemEdit" (ngSubmit)="addComment($event)" #commentForm>
<ion-item> <ion-item>
<ion-textarea placeholder="{{ 'core.comments.addcomment' | translate }}" rows="5" [(ngModel)]="content" name="content" required="required"></ion-textarea> <ion-textarea placeholder="{{ 'core.comments.addcomment' | translate }}" rows="5" [(ngModel)]="content" name="content" required="required"></ion-textarea>
</ion-item> </ion-item>

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, ViewController, NavParams } from 'ionic-angular'; import { IonicPage, ViewController, NavParams } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreCommentsProvider } from '../../providers/comments'; import { CoreCommentsProvider } from '../../providers/comments';
@ -27,6 +29,8 @@ import { CoreCommentsProvider } from '../../providers/comments';
templateUrl: 'add.html', templateUrl: 'add.html',
}) })
export class CoreCommentsAddPage { export class CoreCommentsAddPage {
@ViewChild('commentForm') formElement: ElementRef;
protected contextLevel: string; protected contextLevel: string;
protected instanceId: number; protected instanceId: number;
protected componentName: string; protected componentName: string;
@ -36,8 +40,13 @@ export class CoreCommentsAddPage {
content = ''; content = '';
processing = false; processing = false;
constructor(params: NavParams, private viewCtrl: ViewController, private appProvider: CoreAppProvider, constructor(params: NavParams,
private domUtils: CoreDomUtilsProvider, private commentsProvider: CoreCommentsProvider) { protected viewCtrl: ViewController,
protected appProvider: CoreAppProvider,
protected domUtils: CoreDomUtilsProvider,
protected commentsProvider: CoreCommentsProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) {
this.contextLevel = params.get('contextLevel'); this.contextLevel = params.get('contextLevel');
this.instanceId = params.get('instanceId'); this.instanceId = params.get('instanceId');
this.componentName = params.get('componentName'); this.componentName = params.get('componentName');
@ -61,6 +70,12 @@ export class CoreCommentsAddPage {
this.processing = true; this.processing = true;
this.commentsProvider.addComment(this.content, this.contextLevel, this.instanceId, this.componentName, this.itemId, this.commentsProvider.addComment(this.content, this.contextLevel, this.instanceId, this.componentName, this.itemId,
this.area).then((commentsResponse) => { this.area).then((commentsResponse) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: !!commentsResponse,
}, this.sitesProvider.getCurrentSiteId());
this.viewCtrl.dismiss({comments: commentsResponse}).finally(() => { this.viewCtrl.dismiss({comments: commentsResponse}).finally(() => {
this.domUtils.showToast(commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline', true, this.domUtils.showToast(commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline', true,
3000); 3000);

View File

@ -54,7 +54,7 @@ export class CoreCommentsProvider {
// Convenience function to store a comment to be synchronized later. // Convenience function to store a comment to be synchronized later.
const storeOffline = (): Promise<any> => { const storeOffline = (): Promise<any> => {
return this.commentsOffline.saveComment(content, contextLevel, instanceId, component, itemId, area, siteId).then(() => { return this.commentsOffline.saveComment(content, contextLevel, instanceId, component, itemId, area, siteId).then(() => {
return Promise.resolve(false); return false;
}); });
}; };

View File

@ -10,7 +10,7 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<form ion-list #f="ngForm" (ngSubmit)="submitPassword($event, f.value.password)"> <form ion-list #f="ngForm" (ngSubmit)="submitPassword($event, f.value.password)" #enrolPasswordForm>
<ion-item> <ion-item>
<core-show-password item-content [name]="'password'"> <core-show-password item-content [name]="'password'">
<ion-input text-wrap class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.courses.password' | translate }}" ngModel [core-auto-focus] [clearOnEdit]="false"></ion-input> <ion-input text-wrap class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.courses.password' | translate }}" ngModel [core-auto-focus] [clearOnEdit]="false"></ion-input>

View File

@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, ViewController } from 'ionic-angular'; import { IonicPage, ViewController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
/** /**
* Page that displays a form to enter a password to self enrol in a course. * Page that displays a form to enter a password to self enrol in a course.
@ -24,7 +26,12 @@ import { IonicPage, ViewController } from 'ionic-angular';
templateUrl: 'self-enrol-password.html', templateUrl: 'self-enrol-password.html',
}) })
export class CoreCoursesSelfEnrolPasswordPage { export class CoreCoursesSelfEnrolPasswordPage {
constructor(private viewCtrl: ViewController) { }
@ViewChild('enrolPasswordForm') formElement: ElementRef;
constructor(protected viewCtrl: ViewController,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) { }
/** /**
* Close help modal. * Close help modal.
@ -43,6 +50,11 @@ export class CoreCoursesSelfEnrolPasswordPage {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: false,
}, this.sitesProvider.getCurrentSiteId());
this.viewCtrl.dismiss(password); this.viewCtrl.dismiss(password);
} }
} }

View File

@ -67,7 +67,8 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
protected valueChangeSubscription: Subscription; protected valueChangeSubscription: Subscription;
protected keyboardObs: any; protected keyboardObs: any;
protected initHeightInterval; protected resetObs: any;
protected initHeightInterval: NodeJS.Timer;
rteEnabled = false; rteEnabled = false;
editorSupported = true; editorSupported = true;
@ -175,11 +176,11 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
} }
if (this.shouldAutoSaveDrafts()) { if (this.shouldAutoSaveDrafts()) {
// Recover drafts.
this.restoreDraft(); this.restoreDraft();
// Auto save drafts every certain time.
this.autoSaveDrafts(); this.autoSaveDrafts();
this.deleteDraftOnSubmit();
} }
} }
@ -780,6 +781,25 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
}, this.DRAFT_AUTOSAVE_FREQUENCY); }, this.DRAFT_AUTOSAVE_FREQUENCY);
} }
/**
* Delete the draft when the form is submitted.
*/
protected deleteDraftOnSubmit(): void {
this.resetObs = this.events.on(CoreEventsProvider.FORM_SUBMITTED, async (data) => {
const form = this.element.closest('form');
if (data.form && form && data.form == form) {
try {
await this.editorOffline.deleteDraft(this.contextLevel, this.contextInstanceId, this.elementId,
this.draftExtraParams);
} catch (error) {
// Error deleting draft. Shouldn't happen.
}
}
}, this.sitesProvider.getCurrentSiteId());
}
/** /**
* Show a message. * Show a message.
* *
@ -824,5 +844,6 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
this.keyboardObs && this.keyboardObs.off(); this.keyboardObs && this.keyboardObs.off();
clearInterval(this.autoSaveInterval); clearInterval(this.autoSaveInterval);
clearTimeout(this.hideMessageTimeout); clearTimeout(this.hideMessageTimeout);
this.resetObs && this.resetObs.off();
} }
} }

View File

@ -23,7 +23,7 @@
<p *ngIf="siteName" padding class="item-heading core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></p> <p *ngIf="siteName" padding class="item-heading core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></p>
<p *ngIf="siteName" class="core-siteurl">{{siteUrl}}</p> <p *ngIf="siteName" class="core-siteurl">{{siteUrl}}</p>
</div> </div>
<form ion-list [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form"> <form ion-list [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm>
<ion-item *ngIf="siteChecked && !isBrowserSSO"> <ion-item *ngIf="siteChecked && !isBrowserSSO">
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" formControlName="username" autocapitalize="none" autocorrect="off"></ion-input> <ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" formControlName="username" autocapitalize="none" autocorrect="off"></ion-input>
</ion-item> </ion-item>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
@ -32,6 +32,9 @@ import { CoreConfigConstants } from '../../../../configconstants';
templateUrl: 'credentials.html', templateUrl: 'credentials.html',
}) })
export class CoreLoginCredentialsPage { export class CoreLoginCredentialsPage {
@ViewChild('credentialsForm') formElement: ElementRef;
credForm: FormGroup; credForm: FormGroup;
siteUrl: string; siteUrl: string;
siteChecked = false; siteChecked = false;
@ -242,6 +245,11 @@ export class CoreLoginCredentialsPage {
} }
}).finally(() => { }).finally(() => {
modal.dismiss(); modal.dismiss();
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
});
}); });
} }

View File

@ -17,7 +17,7 @@
<core-loading [hideUntil]="settingsLoaded" *ngIf="!isMinor"> <core-loading [hideUntil]="settingsLoaded" *ngIf="!isMinor">
<!-- Age verification. --> <!-- Age verification. -->
<form ion-list *ngIf="settingsLoaded && settings && ageDigitalConsentVerification" [formGroup]="ageVerificationForm" (ngSubmit)="verifyAge($event)"> <form ion-list *ngIf="settingsLoaded && settings && ageDigitalConsentVerification" [formGroup]="ageVerificationForm" (ngSubmit)="verifyAge($event)" #ageForm>
<ion-item-divider text-wrap> <ion-item-divider text-wrap>
<p class="item-heading">{{ 'core.agelocationverification' | translate }}</p> <p class="item-heading">{{ 'core.agelocationverification' | translate }}</p>
</ion-item-divider> </ion-item-divider>
@ -47,7 +47,7 @@
</form> </form>
<!-- Signup form. --> <!-- Signup form. -->
<form ion-list *ngIf="settingsLoaded && settings && !ageDigitalConsentVerification" [formGroup]="signupForm" (ngSubmit)="create($event)"> <form ion-list *ngIf="settingsLoaded && settings && !ageDigitalConsentVerification" [formGroup]="signupForm" (ngSubmit)="create($event)" #signupFormEl>
<ion-item text-wrap text-center> <ion-item text-wrap text-center>
<!-- If no sitename show big siteurl. --> <!-- If no sitename show big siteurl. -->
<p *ngIf="!siteName" padding class="item-heading">{{siteUrl}}</p> <p *ngIf="!siteName" padding class="item-heading">{{siteUrl}}</p>

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, NavParams, Content } from 'ionic-angular'; import { IonicPage, NavController, NavParams, Content } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -35,6 +36,8 @@ import { CoreConfigConstants } from '../../../../configconstants';
}) })
export class CoreLoginEmailSignupPage { export class CoreLoginEmailSignupPage {
@ViewChild(Content) content: Content; @ViewChild(Content) content: Content;
@ViewChild('ageForm') ageFormElement: ElementRef;
@ViewChild('signupFormEl') signupFormElement: ElementRef;
signupForm: FormGroup; signupForm: FormGroup;
siteUrl: string; siteUrl: string;
@ -66,10 +69,18 @@ export class CoreLoginEmailSignupPage {
policyErrors: any; policyErrors: any;
namefieldsErrors: any; namefieldsErrors: any;
constructor(private navCtrl: NavController, navParams: NavParams, private fb: FormBuilder, private wsProvider: CoreWSProvider, constructor(protected navCtrl: NavController,
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, navParams: NavParams,
private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, protected fb: FormBuilder,
private textUtils: CoreTextUtilsProvider, private userProfileFieldDelegate: CoreUserProfileFieldDelegate) { protected wsProvider: CoreWSProvider,
protected sitesProvider: CoreSitesProvider,
protected loginHelper: CoreLoginHelperProvider,
protected domUtils: CoreDomUtilsProvider,
protected translate: TranslateService,
protected utils: CoreUtilsProvider,
protected textUtils: CoreTextUtilsProvider,
protected userProfileFieldDelegate: CoreUserProfileFieldDelegate,
protected eventsProvider: CoreEventsProvider) {
this.siteUrl = navParams.get('siteUrl'); this.siteUrl = navParams.get('siteUrl');
@ -265,6 +276,12 @@ export class CoreLoginEmailSignupPage {
return this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl }); return this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl });
}).then((result) => { }).then((result) => {
if (result.success) { if (result.success) {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.signupFormElement.nativeElement,
online: true,
});
// Show alert and ho back. // Show alert and ho back.
const message = this.translate.instant('core.login.emailconfirmsent', { $a: params.email }); const message = this.translate.instant('core.login.emailconfirmsent', { $a: params.email });
this.domUtils.showAlert(this.translate.instant('core.success'), message); this.domUtils.showAlert(this.translate.instant('core.success'), message);
@ -334,6 +351,12 @@ export class CoreLoginEmailSignupPage {
params.age = parseInt(params.age, 10); // Use just the integer part. params.age = parseInt(params.age, 10); // Use just the integer part.
this.wsProvider.callAjax('core_auth_is_minor', params, {siteUrl: this.siteUrl}).then((result) => { this.wsProvider.callAjax('core_auth_is_minor', params, {siteUrl: this.siteUrl}).then((result) => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.ageFormElement.nativeElement,
online: true,
});
if (!result.status) { if (!result.status) {
if (this.countryControl.value) { if (this.countryControl.value) {
this.signUpCountryControl.setValue(this.countryControl.value); this.signUpCountryControl.setValue(this.countryControl.value);

View File

@ -10,7 +10,7 @@
</ion-item> </ion-item>
</ion-list> </ion-list>
<ion-card> <ion-card>
<form ion-list [formGroup]="myForm" (ngSubmit)="resetPassword($event)"> <form ion-list [formGroup]="myForm" (ngSubmit)="resetPassword($event)" #resetPasswordForm>
<ion-item-divider text-wrap> <ion-item-divider text-wrap>
{{ 'core.login.searchby' | translate }} {{ 'core.login.searchby' | translate }}
</ion-item-divider> </ion-item-divider>

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreLoginHelperProvider } from '../../providers/helper'; import { CoreLoginHelperProvider } from '../../providers/helper';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -28,11 +30,20 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
templateUrl: 'forgotten-password.html', templateUrl: 'forgotten-password.html',
}) })
export class CoreLoginForgottenPasswordPage { export class CoreLoginForgottenPasswordPage {
@ViewChild('resetPasswordForm') formElement: ElementRef;
myForm: FormGroup; myForm: FormGroup;
siteUrl: string; siteUrl: string;
constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private translate: TranslateService, constructor(protected navCtrl: NavController,
private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider) { navParams: NavParams,
fb: FormBuilder,
protected translate: TranslateService,
protected loginHelper: CoreLoginHelperProvider,
protected domUtils: CoreDomUtilsProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider) {
this.siteUrl = navParams.get('siteUrl'); this.siteUrl = navParams.get('siteUrl');
this.myForm = fb.group({ this.myForm = fb.group({
@ -71,6 +82,11 @@ export class CoreLoginForgottenPasswordPage {
this.domUtils.showErrorModal(response.notice); this.domUtils.showErrorModal(response.notice);
} else { } else {
// Success. // Success.
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
});
this.domUtils.showAlert(this.translate.instant('core.success'), response.notice); this.domUtils.showAlert(this.translate.instant('core.success'), response.notice);
this.navCtrl.pop(); this.navCtrl.pop();
} }

View File

@ -29,7 +29,7 @@
<ion-icon padding name="alert"></ion-icon> {{ 'core.login.reconnectdescription' | translate }} <ion-icon padding name="alert"></ion-icon> {{ 'core.login.reconnectdescription' | translate }}
</p> </p>
</div> </div>
<form ion-list *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form"> <form ion-list *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
<ion-item text-wrap class="core-username"> <ion-item text-wrap class="core-username">
<p>{{username}}</p> <p>{{username}}</p>
</ion-item> </ion-item>

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreLoginHelperProvider } from '../../providers/helper'; import { CoreLoginHelperProvider } from '../../providers/helper';
@ -29,6 +30,9 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
templateUrl: 'reconnect.html', templateUrl: 'reconnect.html',
}) })
export class CoreLoginReconnectPage { export class CoreLoginReconnectPage {
@ViewChild('reconnectForm') formElement: ElementRef;
credForm: FormGroup; credForm: FormGroup;
siteUrl: string; siteUrl: string;
username: string; username: string;
@ -47,13 +51,14 @@ export class CoreLoginReconnectPage {
protected isLoggedOut: boolean; protected isLoggedOut: boolean;
protected siteId: string; protected siteId: string;
constructor(private navCtrl: NavController, constructor(protected navCtrl: NavController,
navParams: NavParams, navParams: NavParams,
fb: FormBuilder, fb: FormBuilder,
private appProvider: CoreAppProvider, protected appProvider: CoreAppProvider,
private sitesProvider: CoreSitesProvider, protected sitesProvider: CoreSitesProvider,
private loginHelper: CoreLoginHelperProvider, protected loginHelper: CoreLoginHelperProvider,
private domUtils: CoreDomUtilsProvider) { protected domUtils: CoreDomUtilsProvider,
protected eventsProvider: CoreEventsProvider) {
const currentSite = this.sitesProvider.getCurrentSite(); const currentSite = this.sitesProvider.getCurrentSite();
@ -175,6 +180,12 @@ export class CoreLoginReconnectPage {
// Start the authentication process. // Start the authentication process.
this.sitesProvider.getUserToken(siteUrl, username, password).then((data) => { this.sitesProvider.getUserToken(siteUrl, username, password).then((data) => {
return this.sitesProvider.updateSiteToken(this.infoSiteUrl, username, data.token, data.privateToken).then(() => { return this.sitesProvider.updateSiteToken(this.infoSiteUrl, username, data.token, data.privateToken).then(() => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
});
// Update site info too because functions might have changed (e.g. unisntall local_mobile). // Update site info too because functions might have changed (e.g. unisntall local_mobile).
return this.sitesProvider.updateSiteInfoByUrl(this.infoSiteUrl, username).then(() => { return this.sitesProvider.updateSiteInfoByUrl(this.infoSiteUrl, username).then(() => {
// Reset fields so the data is not in the view anymore. // Reset fields so the data is not in the view anymore.

View File

@ -17,7 +17,7 @@
<div text-center padding> <div text-center padding>
<img src="assets/img/login_logo.png" class="avatar-full login-logo" role="presentation"> <img src="assets/img/login_logo.png" class="avatar-full login-logo" role="presentation">
</div> </div>
<form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || fixedDisplay == 'select'"> <form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || fixedDisplay == 'select'" #siteFormEl>
<!-- Form to input the site URL if there are no fixed sites. --> <!-- Form to input the site URL if there are no fixed sites. -->
<ng-container *ngIf="!fixedSites"> <ng-container *ngIf="!fixedSites">
<p padding>{{ 'core.login.newsitedescription' | translate }}</p> <p padding>{{ 'core.login.newsitedescription' | translate }}</p>

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, ModalController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, ModalController, NavParams } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider, CoreSiteCheckResponse } from '@providers/sites'; import { CoreSitesProvider, CoreSiteCheckResponse } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreConfigConstants } from '../../../../configconstants'; import { CoreConfigConstants } from '../../../../configconstants';
@ -31,6 +32,9 @@ import { CoreUrl } from '@classes/utils/url';
templateUrl: 'site.html', templateUrl: 'site.html',
}) })
export class CoreLoginSitePage { export class CoreLoginSitePage {
@ViewChild('siteFormEl') formElement: ElementRef;
siteForm: FormGroup; siteForm: FormGroup;
fixedSites: any[]; fixedSites: any[];
filteredSites: any[]; filteredSites: any[];
@ -38,9 +42,15 @@ export class CoreLoginSitePage {
showKeyboard = false; showKeyboard = false;
filter = ''; filter = '';
constructor(navParams: NavParams, private navCtrl: NavController, fb: FormBuilder, private appProvider: CoreAppProvider, constructor(navParams: NavParams,
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, protected navCtrl: NavController,
private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider) { fb: FormBuilder,
protected appProvider: CoreAppProvider,
protected sitesProvider: CoreSitesProvider,
protected loginHelper: CoreLoginHelperProvider,
protected modalCtrl: ModalController,
protected domUtils: CoreDomUtilsProvider,
protected eventsProvider: CoreEventsProvider) {
this.showKeyboard = !!navParams.get('showKeyboard'); this.showKeyboard = !!navParams.get('showKeyboard');
@ -96,6 +106,12 @@ export class CoreLoginSitePage {
// It's a demo site. // It's a demo site.
this.sitesProvider.getUserToken(siteData.url, siteData.username, siteData.password).then((data) => { this.sitesProvider.getUserToken(siteData.url, siteData.username, siteData.password).then((data) => {
return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken).then(() => { return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken).then(() => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
});
return this.loginHelper.goToSiteInitialPage(); return this.loginHelper.goToSiteInitialPage();
}, (error) => { }, (error) => {
this.loginHelper.treatUserTokenError(siteData.url, error, siteData.username, siteData.password); this.loginHelper.treatUserTokenError(siteData.url, error, siteData.username, siteData.password);
@ -175,6 +191,12 @@ export class CoreLoginSitePage {
*/ */
protected async login(response: CoreSiteCheckResponse): Promise<void> { protected async login(response: CoreSiteCheckResponse): Promise<void> {
return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => { return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
this.eventsProvider.trigger(CoreEventsProvider.FORM_SUBMITTED, {
form: this.formElement.nativeElement,
online: true,
});
if (response.warning) { if (response.warning) {
this.domUtils.showErrorModal(response.warning, true, 4000); this.domUtils.showErrorModal(response.warning, true, 4000);
} }

View File

@ -1,5 +1,5 @@
<ion-card> <ion-card>
<form #f="ngForm" (ngSubmit)="submitForm($event)" role="search"> <form #f="ngForm" (ngSubmit)="submitForm($event)" role="search" #searchForm>
<ion-item> <ion-item>
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (focus)="showHistory()" (blur)="hideHistory()"></ion-input> <ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (focus)="showHistory()" (blur)="hideHistory()"></ion-input>
<button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="disabled || !searchText || (searchText.length < lengthCheck)"> <button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="disabled || !searchText || (searchText.length < lengthCheck)">

View File

@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSearchHistoryProvider, CoreSearchHistoryItem } from '../../providers/search-history'; import { CoreSearchHistoryProvider, CoreSearchHistoryItem } from '../../providers/search-history';
@ -47,6 +50,8 @@ export class CoreSearchBoxComponent implements OnInit, OnDestroy {
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form. @Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
@Output() onClear: EventEmitter<void>; // Send event when clearing the search form. @Output() onClear: EventEmitter<void>; // Send event when clearing the search form.
@ViewChild('searchForm') formElement: ElementRef;
searched = ''; // Last search emitted. searched = ''; // Last search emitted.
searchText = ''; searchText = '';
history: CoreSearchHistoryItem[]; history: CoreSearchHistoryItem[];
@ -58,6 +63,9 @@ export class CoreSearchBoxComponent implements OnInit, OnDestroy {
constructor(protected translate: TranslateService, constructor(protected translate: TranslateService,
protected utils: CoreUtilsProvider, protected utils: CoreUtilsProvider,
protected searchHistoryProvider: CoreSearchHistoryProvider, protected searchHistoryProvider: CoreSearchHistoryProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider,
protected domUtils: CoreDomUtilsProvider,
) { ) {
this.onSubmit = new EventEmitter<string>(); this.onSubmit = new EventEmitter<string>();
this.onClear = new EventEmitter<void>(); this.onClear = new EventEmitter<void>();
@ -95,6 +103,8 @@ export class CoreSearchBoxComponent implements OnInit, OnDestroy {
this.saveSearchToHistory(this.searchText); this.saveSearchToHistory(this.searchText);
} }
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
this.searched = this.searchText; this.searched = this.searchText;
this.onSubmit.emit(this.searchText); this.onSubmit.emit(this.searchText);
} }

View File

@ -64,6 +64,7 @@ export class CoreEventsProvider {
static SELECT_COURSE_TAB = 'select_course_tab'; static SELECT_COURSE_TAB = 'select_course_tab';
static WS_CACHE_INVALIDATED = 'ws_cache_invalidated'; static WS_CACHE_INVALIDATED = 'ws_cache_invalidated';
static SITE_STORAGE_DELETED = 'site_storage_deleted'; static SITE_STORAGE_DELETED = 'site_storage_deleted';
static FORM_SUBMITTED = 'form_submitted';
protected logger; protected logger;
protected observables: { [s: string]: Subject<any> } = {}; protected observables: { [s: string]: Subject<any> } = {};