Merge pull request #2564 from crazyserver/MOBILE-3200

Mobile 3200
main
Juan Leyva 2020-10-16 13:18:26 +02:00 committed by GitHub
commit 1f1912bf98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 338 additions and 289 deletions

View File

@ -507,11 +507,13 @@
"addon.mod_data.foundrecords": "data", "addon.mod_data.foundrecords": "data",
"addon.mod_data.gettinglocation": "local_moodlemobileapp", "addon.mod_data.gettinglocation": "local_moodlemobileapp",
"addon.mod_data.latlongboth": "data", "addon.mod_data.latlongboth": "data",
"addon.mod_data.locationnotenabled": "local_moodlemobileapp",
"addon.mod_data.locationpermissiondenied": "local_moodlemobileapp", "addon.mod_data.locationpermissiondenied": "local_moodlemobileapp",
"addon.mod_data.menuchoose": "data", "addon.mod_data.menuchoose": "data",
"addon.mod_data.modulenameplural": "data", "addon.mod_data.modulenameplural": "data",
"addon.mod_data.more": "data", "addon.mod_data.more": "data",
"addon.mod_data.mylocation": "local_moodlemobileapp", "addon.mod_data.mylocation": "local_moodlemobileapp",
"addon.mod_data.noaccess": "data",
"addon.mod_data.nomatch": "data", "addon.mod_data.nomatch": "data",
"addon.mod_data.norecords": "data", "addon.mod_data.norecords": "data",
"addon.mod_data.notapproved": "data", "addon.mod_data.notapproved": "data",

View File

@ -180,29 +180,34 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
* @param showErrors If show errors to the user of hide them. * @param showErrors If show errors to the user of hide them.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> { protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
let canAdd = false, let canAdd = false,
canSearch = false; canSearch = false;
return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { this.data = await this.dataProvider.getDatabase(this.courseId, this.module.id);
this.data = data; this.hasComments = this.data.comments;
this.hasComments = data.comments;
this.description = data.intro || data.description; this.description = this.data.intro || this.data.description;
this.dataRetrieved.emit(data); this.dataRetrieved.emit(this.data);
if (sync) { if (sync) {
try {
// Try to synchronize the data. // Try to synchronize the data.
return this.syncActivity(showErrors).catch(() => { await this.syncActivity(showErrors);
} catch (error) {
// Ignore errors. // Ignore errors.
});
} }
}).then(() => { }
return this.dataProvider.getDatabaseAccessInformation(this.data.id, {cmId: this.module.id});
}).then((accessData) => {
this.access = accessData;
if (!accessData.timeavailable) { this.groupInfo = await this.groupsProvider.getActivityGroupInfo(this.data.coursemodule);
this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, this.groupInfo);
this.access = await this.dataProvider.getDatabaseAccessInformation(this.data.id, {
cmId: this.module.id,
groupId: this.selectedGroup || undefined
});
if (!this.access.timeavailable) {
const time = this.timeUtils.timestamp(); const time = this.timeUtils.timestamp();
this.timeAvailableFrom = this.data.timeavailablefrom && time < this.data.timeavailablefrom ? this.timeAvailableFrom = this.data.timeavailablefrom && time < this.data.timeavailablefrom ?
@ -214,35 +219,28 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
this.isEmpty = true; this.isEmpty = true;
this.groupInfo = null; this.groupInfo = null;
} else {
return;
}
canSearch = true; canSearch = true;
canAdd = accessData.canaddentry; canAdd = this.access.canaddentry;
return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule).then((groupInfo) => {
this.groupInfo = groupInfo;
this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, groupInfo);
});
}).then(() => {
return this.dataProvider.getFields(this.data.id, {cmId: this.module.id}).then((fields) => {
if (fields.length == 0) {
canSearch = false;
canAdd = false;
} }
const fields = await this.dataProvider.getFields(this.data.id, {cmId: this.module.id});
this.search.advanced = []; this.search.advanced = [];
this.fields = this.utils.arrayToObject(fields, 'id'); this.fields = this.utils.arrayToObject(fields, 'id');
this.fieldsArray = this.utils.objectToArray(this.fields); this.fieldsArray = this.utils.objectToArray(this.fields);
if (this.fieldsArray.length == 0) {
canSearch = false;
canAdd = false;
}
return this.fetchEntriesData(); try {
}); await this.fetchEntriesData();
}).finally(() => { } finally {
this.canAdd = canAdd; this.canAdd = canAdd;
this.canSearch = canSearch; this.canSearch = canSearch;
this.fillContextMenu(refresh); this.fillContextMenu(refresh);
}); }
} }
/** /**
@ -252,13 +250,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
*/ */
protected fetchEntriesData(): Promise<any> { protected fetchEntriesData(): Promise<any> {
return this.dataProvider.getDatabaseAccessInformation(this.data.id, {
groupId: this.selectedGroup,
cmId: this.module.id,
}).then((accessData) => {
// Update values for current group.
this.access.canaddentry = accessData.canaddentry;
const search = this.search.searching && !this.search.searchingAdvanced ? this.search.text : undefined; const search = this.search.searching && !this.search.searchingAdvanced ? this.search.text : undefined;
const advSearch = this.search.searching && this.search.searchingAdvanced ? this.search.advanced : undefined; const advSearch = this.search.searching && this.search.searchingAdvanced ? this.search.advanced : undefined;
@ -269,7 +260,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
sort: Number(this.search.sortBy), sort: Number(this.search.sortBy),
order: this.search.sortDirection, order: this.search.sortDirection,
page: this.search.page, page: this.search.page,
});
}).then((entries) => { }).then((entries) => {
const numEntries = entries.entries.length; const numEntries = entries.entries.length;
const numOfflineEntries = entries.offlineEntries.length; const numOfflineEntries = entries.offlineEntries.length;
@ -390,18 +380,29 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
* @param groupId Group ID. * @param groupId Group ID.
* @return Resolved when new group is selected or rejected if not. * @return Resolved when new group is selected or rejected if not.
*/ */
setGroup(groupId: number): Promise<any> { async setGroup(groupId: number): Promise<void> {
this.selectedGroup = groupId; this.selectedGroup = groupId;
this.search.page = 0; this.search.page = 0;
return this.fetchEntriesData().then(() => { // Only update canAdd if there's any field, otheerwise, canAdd will remain false.
if (this.fieldsArray.length > 0) {
// Update values for current group.
this.access = await this.dataProvider.getDatabaseAccessInformation(this.data.id, {
groupId: this.selectedGroup,
cmId: this.module.id,
});
this.canAdd = this.access.canaddentry;
}
try {
await this.fetchEntriesData();
// Log activity view for coherence with Moodle web. // Log activity view for coherence with Moodle web.
return this.logView(); return this.logView();
}).catch((message) => { } catch (error) {
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
}
return Promise.reject(null);
});
} }
/** /**

View File

@ -28,6 +28,7 @@
"modulenameplural": "Databases", "modulenameplural": "Databases",
"more": "More", "more": "More",
"mylocation": "My location", "mylocation": "My location",
"noaccess": "You do not have access to this page",
"nomatch": "No matching entries found!", "nomatch": "No matching entries found!",
"norecords": "No entries in database", "norecords": "No entries in database",
"notapproved": "Entry is not approved yet.", "notapproved": "Entry is not approved yet.",

View File

@ -18,8 +18,8 @@
</ion-select> </ion-select>
</ion-item> </ion-item>
<div class="addon-data-contents addon-data-entries-{{data.id}}" *ngIf="data"> <div class="addon-data-contents {{cssClass}}" *ngIf="data">
<core-style [css]="data.csstemplate" prefix=".addon-data-entries-{{data.id}}"></core-style> <core-style [css]="data.csstemplate" prefix=".{{cssClass}}"></core-style>
<form (ngSubmit)="save($event)" [formGroup]="editForm" #editFormEl> <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>

View File

@ -52,6 +52,8 @@ export class AddonModDataEditPage {
protected siteId: string; protected siteId: string;
protected offline: boolean; protected offline: boolean;
protected forceLeave = false; // To allow leaving the page without checking for changes. protected forceLeave = false; // To allow leaving the page without checking for changes.
protected initialSelectedGroup = null;
protected isEditing = false;
title = ''; title = '';
component = AddonModDataProvider.COMPONENT; component = AddonModDataProvider.COMPONENT;
@ -75,7 +77,10 @@ export class AddonModDataEditPage {
this.module = params.get('module') || {}; this.module = params.get('module') || {};
this.entryId = params.get('entryId') || null; this.entryId = params.get('entryId') || null;
this.courseId = params.get('courseId'); this.courseId = params.get('courseId');
this.selectedGroup = params.get('group') || 0; this.selectedGroup = this.entryId ? null : (params.get('group') || 0);
// If entryId is lower than 0 or null, it is a new entry or an offline entry.
this.isEditing = this.entryId && this.entryId > 0;
this.siteId = sitesProvider.getCurrentSiteId(); this.siteId = sitesProvider.getCurrentSiteId();
@ -88,7 +93,7 @@ export class AddonModDataEditPage {
* View loaded. * View loaded.
*/ */
ionViewDidLoad(): void { ionViewDidLoad(): void {
this.fetchEntryData(); this.fetchEntryData(true);
} }
/** /**
@ -103,7 +108,8 @@ export class AddonModDataEditPage {
const inputData = this.editForm.value; const inputData = this.editForm.value;
const changed = await this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, this.entry.contents); let changed = await this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, this.entry.contents);
changed = changed || (!this.isEditing && this.initialSelectedGroup != this.selectedGroup);
if (changed) { if (changed) {
// Show confirmation if some data has been modified. // Show confirmation if some data has been modified.
@ -120,38 +126,78 @@ export class AddonModDataEditPage {
/** /**
* Fetch the entry data. * Fetch the entry data.
* *
* @param [refresh] To refresh all downloaded data.
* @return Resolved when done. * @return Resolved when done.
*/ */
protected fetchEntryData(): Promise<any> { protected async fetchEntryData(refresh: boolean = false): Promise<void> {
return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { try {
this.title = data.name || this.title; this.data = await this.dataProvider.getDatabase(this.courseId, this.module.id);
this.data = data; this.title = this.data.name || this.title;
this.cssClass = 'addon-data-entries-' + data.id; this.cssClass = 'addon-data-entries-' + this.data.id;
return this.dataProvider.getDatabaseAccessInformation(data.id, {cmId: this.module.id}); this.fieldsArray = await this.dataProvider.getFields(this.data.id, {cmId: this.module.id});
}).then((accessData) => { this.fields = this.utils.arrayToObject(this.fieldsArray, 'id');
if (this.entryId) {
return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule).then((groupInfo) => { const entry = await this.dataHelper.fetchEntry(this.data, this.fieldsArray, this.entryId);
this.groupInfo = groupInfo;
this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, groupInfo);
});
}
}).then(() => {
return this.dataProvider.getFields(this.data.id, {cmId: this.module.id});
}).then((fieldsData) => {
this.fieldsArray = fieldsData;
this.fields = this.utils.arrayToObject(fieldsData, 'id');
return this.dataHelper.fetchEntry(this.data, fieldsData, this.entryId);
}).then((entry) => {
this.entry = entry.entry; this.entry = entry.entry;
this.editFormRender = this.displayEditFields(); // Load correct group.
}).catch((message) => { this.selectedGroup = this.selectedGroup == null ? this.entry.groupid : this.selectedGroup;
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
}).finally(() => { // Check permissions when adding a new entry or offline entry.
this.loaded = true; if (!this.isEditing) {
let haveAccess = false;
if (refresh) {
this.groupInfo = await this.groupsProvider.getActivityGroupInfo(this.data.coursemodule);
this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, this.groupInfo);
this.initialSelectedGroup = this.selectedGroup;
}
if (this.groupInfo.groups.length > 0) {
if (refresh) {
const canAddGroup = {};
await Promise.all(this.groupInfo.groups.map(async (group) => {
const accessData = await this.dataProvider.getDatabaseAccessInformation(this.data.id, {
cmId: this.module.id, groupId: group.id});
canAddGroup[group.id] = accessData.canaddentry;
}));
this.groupInfo.groups = this.groupInfo.groups.filter((group) => {
return !!canAddGroup[group.id];
}); });
haveAccess = canAddGroup[this.selectedGroup];
} else {
// Groups already filtered, so it have access.
haveAccess = true;
}
} else {
const accessData = await this.dataProvider.getDatabaseAccessInformation(this.data.id, {cmId: this.module.id});
haveAccess = accessData.canaddentry;
}
if (!haveAccess) {
// You shall not pass, go back.
this.domUtils.showErrorModal('addon.mod_data.noaccess', true);
// Go back to entry list.
this.forceLeave = true;
this.navCtrl.pop();
return;
}
}
this.editFormRender = this.displayEditFields();
} catch (message) {
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
}
this.loaded = true;
} }
/** /**
@ -160,7 +206,7 @@ export class AddonModDataEditPage {
* @param e Event. * @param e Event.
* @return Resolved when done. * @return Resolved when done.
*/ */
save(e: Event): Promise<any> { save(e: Event): Promise<void> {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -169,6 +215,7 @@ export class AddonModDataEditPage {
return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id,
this.entry.contents).then((changed) => { this.entry.contents).then((changed) => {
changed = changed || (!this.isEditing && this.initialSelectedGroup != this.selectedGroup);
if (!changed) { if (!changed) {
if (this.entryId) { if (this.entryId) {
return this.returnToEntryList(); return this.returnToEntryList();
@ -196,7 +243,7 @@ export class AddonModDataEditPage {
return Promise.reject(e); return Promise.reject(e);
}).then((editData) => { }).then((editData) => {
if (editData.length > 0) { if (editData.length > 0) {
if (this.entryId) { if (this.isEditing) {
return this.dataProvider.editEntry(this.data.id, this.entryId, this.courseId, editData, this.fields, return this.dataProvider.editEntry(this.data.id, this.entryId, this.courseId, editData, this.fields,
undefined, this.offline); undefined, this.offline);
} }
@ -213,20 +260,20 @@ 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.isEditing && result.updated) || (!this.isEditing && result.newentryid)) {
this.domUtils.triggerFormSubmittedEvent(this.formElement, result.sent, this.siteId); this.domUtils.triggerFormSubmittedEvent(this.formElement, result.sent, this.siteId);
if (result.sent) {
this.eventsProvider.trigger(CoreEventsProvider.ACTIVITY_DATA_SENT, { module: 'data' });
}
const promises = []; const promises = [];
this.entryId = this.entryId || result.newentryid; if (result.sent) {
this.eventsProvider.trigger(CoreEventsProvider.ACTIVITY_DATA_SENT, { module: 'data' });
if (this.isEditing) {
promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId, this.siteId)); promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId, this.siteId));
}
promises.push(this.dataProvider.invalidateEntriesData(this.data.id, this.siteId)); promises.push(this.dataProvider.invalidateEntriesData(this.data.id, this.siteId));
}
return Promise.all(promises).then(() => { return Promise.all(promises).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED,
@ -264,7 +311,7 @@ export class AddonModDataEditPage {
* @param groupId Group identifier to set. * @param groupId Group identifier to set.
* @return Resolved when done. * @return Resolved when done.
*/ */
setGroup(groupId: number): Promise<any> { setGroup(groupId: number): Promise<void> {
this.selectedGroup = groupId; this.selectedGroup = groupId;
this.loaded = false; this.loaded = false;
@ -322,7 +369,7 @@ export class AddonModDataEditPage {
* *
* @return Resolved when done. * @return Resolved when done.
*/ */
protected returnToEntryList(): Promise<any> { protected returnToEntryList(): Promise<void> {
const inputData = this.editForm.value; const inputData = this.editForm.value;
return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id, return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id,

View File

@ -117,46 +117,51 @@ export class AddonModDataProvider {
* @param forceOffline Force editing entry in offline. * @param forceOffline Force editing entry in offline.
* @return Promise resolved when the action is done. * @return Promise resolved when the action is done.
*/ */
addEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], groupId: number = 0, async addEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], groupId: number = 0,
fields: any, siteId?: string, forceOffline: boolean = false): Promise<any> { fields: any, siteId?: string, forceOffline: boolean = false): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// 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 = async (): Promise<any> => {
return this.dataOffline.saveEntry(dataId, entryId, 'add', courseId, groupId, contents, undefined, siteId) const entry = await this.dataOffline.saveEntry(dataId, entryId, 'add', courseId, groupId, contents, undefined, siteId);
.then((entry) => {
return { return {
// Return provissional entry Id. // Return provissional entry Id.
newentryid: entry, newentryid: entry,
sent: false, sent: false,
}; };
});
}; };
// Checks to store offline.
if (!this.appProvider.isOnline() || forceOffline) { if (!this.appProvider.isOnline() || forceOffline) {
const notifications = this.checkFields(fields, contents); const notifications = this.checkFields(fields, contents);
if (notifications) { if (notifications) {
return Promise.resolve({ return { fieldnotifications: notifications };
fieldnotifications: notifications }
});
} }
// Remove unnecessary not synced actions.
await this.deleteEntryOfflineAction(dataId, entryId, 'add', siteId);
// App is offline, store the action.
if (!this.appProvider.isOnline() || forceOffline) {
return storeOffline(); return storeOffline();
} }
return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => { try {
const result = await this.addEntryOnline(dataId, contents, groupId, siteId);
result.sent = true; result.sent = true;
return result; return result;
}).catch((error) => { } 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); throw error;
} }
// Couldn't connect to server, store in offline. // Couldn't connect to server, store in offline.
return storeOffline(); return storeOffline();
}); }
} }
/** /**
@ -193,48 +198,49 @@ export class AddonModDataProvider {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the action is done. * @return Promise resolved when the action is done.
*/ */
approveEntry(dataId: number, entryId: number, approve: boolean, courseId: number, siteId?: string): Promise<any> { async approveEntry(dataId: number, entryId: number, approve: boolean, courseId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// 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 = async (): Promise<any> => {
const action = approve ? 'approve' : 'disapprove'; const action = approve ? 'approve' : 'disapprove';
return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId) await this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId);
.then(() => {
return { return {
sent: false, sent: false,
}; };
});
}; };
// Get if the opposite action is not synced. // Get if the opposite action is not synced.
const oppositeAction = approve ? 'disapprove' : 'approve'; const oppositeAction = approve ? 'disapprove' : 'approve';
return this.dataOffline.getEntry(dataId, entryId, oppositeAction, siteId).then(() => { const found = await this.deleteEntryOfflineAction(dataId, entryId, oppositeAction, siteId);
// Found. Just delete the action. if (found) {
return this.dataOffline.deleteEntry(dataId, entryId, oppositeAction, siteId); // Offline action has been found and deleted. Stop here.
}).catch(() => { return;
}
if (!this.appProvider.isOnline()) { if (!this.appProvider.isOnline()) {
// App is offline, store the action. // App is offline, store the action.
return storeOffline(); return storeOffline();
} }
return this.approveEntryOnline(entryId, approve, siteId).then(() => { try {
await this.approveEntryOnline(entryId, approve, siteId);
return { return {
sent: true, sent: true,
}; };
}).catch((error) => { } 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); throw error;
} }
// Couldn't connect to server, store in offline. // Couldn't connect to server, store in offline.
return storeOffline(); return storeOffline();
}); }
});
} }
/** /**
@ -298,38 +304,22 @@ export class AddonModDataProvider {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the action is done. * @return Promise resolved when the action is done.
*/ */
deleteEntry(dataId: number, entryId: number, courseId: number, siteId?: string): Promise<any> { async deleteEntry(dataId: number, entryId: number, courseId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// 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 = async (): Promise<any> => {
return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId) await this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId);
.then(() => {
return { return {
sent: false, sent: false,
}; };
});
}; };
let justAdded = false;
// Check if the opposite action is not synced and just delete it. // Check if the opposite action is not synced and just delete it.
return this.dataOffline.getEntryActions(dataId, entryId, siteId).then((entries) => { const addedOffline = await this.deleteEntryOfflineAction(dataId, entryId, 'add', siteId);
if (entries && entries.length) { if (addedOffline) {
// Found. Delete other actions first. // Offline add action found and deleted. Stop here.
const proms = entries.map((entry) => {
if (entry.action == 'add') {
justAdded = true;
}
return this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId);
});
return Promise.all(proms);
}
}).then(() => {
if (justAdded) {
// The field was added offline, delete and stop.
return; return;
} }
@ -338,20 +328,21 @@ export class AddonModDataProvider {
return storeOffline(); return storeOffline();
} }
return this.deleteEntryOnline(entryId, siteId).then(() => { try {
await this.deleteEntryOnline(entryId, siteId);
return { return {
sent: true, sent: true,
}; };
}).catch((error) => { } 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); throw error;
} }
// Couldn't connect to server, store in offline. // Couldn't connect to server, store in offline.
return storeOffline(); return storeOffline();
}); }
});
} }
/** /**
@ -371,6 +362,29 @@ export class AddonModDataProvider {
}); });
} }
/**
* Delete entry offline action.
*
* @param dataId Database ID.
* @param entryId Entry ID.
* @param action Action name to delete.
* @param siteId Site ID.
* @return Resolved with true if the action has been found and deleted.
*/
protected async deleteEntryOfflineAction(dataId: number, entryId: number, action: string, siteId: string): Promise<boolean> {
// Get other not not synced actions.
try {
await this.dataOffline.getEntry(dataId, entryId, action, siteId);
await this.dataOffline.deleteEntry(dataId, entryId, action, siteId);
return true;
} catch (error) {
// Not found.
return false;
}
}
/** /**
* Updates an existing entry. * Updates an existing entry.
* *
@ -383,82 +397,50 @@ export class AddonModDataProvider {
* @param forceOffline Force editing entry in offline. * @param forceOffline Force editing entry in offline.
* @return Promise resolved when the action is done. * @return Promise resolved when the action is done.
*/ */
editEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], fields: any, siteId?: string, async editEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], fields: any,
forceOffline: boolean = false): Promise<any> { siteId?: string, forceOffline: boolean = false): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// 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 = async (): Promise<any> => {
return this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId) await this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId);
.then(() => {
return { return {
updated: true, updated: true,
sent: false, sent: false,
}; };
});
}; };
let justAdded = false,
groupId;
if (!this.appProvider.isOnline() || forceOffline) { if (!this.appProvider.isOnline() || forceOffline) {
const notifications = this.checkFields(fields, contents); const notifications = this.checkFields(fields, contents);
if (notifications) { if (notifications) {
return Promise.resolve({ return { fieldnotifications: notifications };
fieldnotifications: notifications
});
} }
} }
// Get other not not synced actions. // Remove unnecessary not synced actions.
return this.dataOffline.getEntryActions(dataId, entryId, siteId).then((entries) => { await this.deleteEntryOfflineAction(dataId, entryId, 'edit', siteId);
if (entries && entries.length) {
// Found. Delete add and edit actions first.
const proms = [];
entries.forEach((entry) => {
if (entry.action == 'add') {
justAdded = true;
groupId = entry.groupid;
proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId));
} else if (entry.action == 'edit') {
proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId));
}
});
return Promise.all(proms);
}
}).then(() => {
if (justAdded) {
// The field was added offline, add again and stop.
return this.addEntry(dataId, entryId, courseId, contents, groupId, fields, siteId, forceOffline)
.then((result) => {
result.updated = true;
result.sent = true;
return result;
});
}
if (!this.appProvider.isOnline() || forceOffline) { if (!this.appProvider.isOnline() || forceOffline) {
// App is offline, store the action. // App is offline, store the action.
return storeOffline(); return storeOffline();
} }
return this.editEntryOnline(entryId, contents, siteId).then((result) => { try {
const result = await this.editEntryOnline(entryId, contents, siteId);
result.sent = true; result.sent = true;
return result; return result;
}).catch((error) => { } 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); throw error;
} }
// Couldn't connect to server, store in offline. // Couldn't connect to server, store in offline.
return storeOffline(); return storeOffline();
});
});
} }
}
/** /**
* Updates an existing entry. It does not cache calls. It will fail if offline or cannot connect. * Updates an existing entry. It does not cache calls. It will fail if offline or cannot connect.

View File

@ -18,7 +18,6 @@ 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';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { AddonModDataFieldsDelegate } from './fields-delegate'; import { AddonModDataFieldsDelegate } from './fields-delegate';
@ -35,12 +34,19 @@ import { CoreRatingOfflineProvider } from '@core/rating/providers/offline';
@Injectable() @Injectable()
export class AddonModDataHelperProvider { export class AddonModDataHelperProvider {
constructor(private sitesProvider: CoreSitesProvider, protected dataProvider: AddonModDataProvider, constructor(
private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate, protected sitesProvider: CoreSitesProvider,
private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider, protected dataProvider: AddonModDataProvider,
private textUtils: CoreTextUtilsProvider, private eventsProvider: CoreEventsProvider, private utils: CoreUtilsProvider, protected translate: TranslateService,
private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
private ratingOffline: CoreRatingOfflineProvider) {} protected dataOffline: AddonModDataOfflineProvider,
protected fileUploaderProvider: CoreFileUploaderProvider,
protected textUtils: CoreTextUtilsProvider,
protected eventsProvider: CoreEventsProvider,
protected domUtils: CoreDomUtilsProvider,
protected courseProvider: CoreCourseProvider,
protected ratingOffline: CoreRatingOfflineProvider
) {}
/** /**
* Returns the record with the offline actions applied. * Returns the record with the offline actions applied.
@ -632,35 +638,44 @@ export class AddonModDataHelperProvider {
* @param courseId Course ID. It not defined, it will be fetched. * @param courseId Course ID. It not defined, it will be fetched.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
*/ */
showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): void { async showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): Promise<void> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
this.domUtils.showDeleteConfirm('addon.mod_data.confirmdeleterecord').then(() => { let modal;
const modal = this.domUtils.showModalLoading(); try {
await this.domUtils.showDeleteConfirm('addon.mod_data.confirmdeleterecord');
return this.getActivityCourseIdIfNotSet(dataId, courseId, siteId).then((courseId) => { modal = this.domUtils.showModalLoading();
return this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId);
}).catch((message) => { try {
if (entryId > 0) {
courseId = await this.getActivityCourseIdIfNotSet(dataId, courseId, siteId);
}
this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId);
} catch (message) {
this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true); this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true);
return Promise.reject(null); modal && modal.dismiss();
}).then(() => {
return this.utils.allPromises([ return;
this.dataProvider.invalidateEntryData(dataId, entryId, siteId), }
this.dataProvider.invalidateEntriesData(dataId, siteId)
]).catch(() => { try {
await this.dataProvider.invalidateEntryData(dataId, entryId, siteId);
await this.dataProvider.invalidateEntriesData(dataId, siteId);
} catch (error) {
// Ignore errors. // Ignore errors.
}); }
}).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId, entryId, deleted: true}, siteId); this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId, entryId, deleted: true}, siteId);
this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000); this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000);
}).finally(() => { } catch (error) {
modal.dismiss();
});
}).catch(() => {
// Ignore error, it was already displayed. // Ignore error, it was already displayed.
}); }
modal && modal.dismiss();
} }
/** /**

View File

@ -513,6 +513,7 @@
"addon.mod_data.modulenameplural": "Databases", "addon.mod_data.modulenameplural": "Databases",
"addon.mod_data.more": "More", "addon.mod_data.more": "More",
"addon.mod_data.mylocation": "My location", "addon.mod_data.mylocation": "My location",
"addon.mod_data.noaccess": "You do not have access to this page",
"addon.mod_data.nomatch": "No matching entries found!", "addon.mod_data.nomatch": "No matching entries found!",
"addon.mod_data.norecords": "No entries in database", "addon.mod_data.norecords": "No entries in database",
"addon.mod_data.notapproved": "Entry is not approved yet.", "addon.mod_data.notapproved": "Entry is not approved yet.",