Merge pull request #1860 from albertgasset/MOBILE-2856

Mobile 2856
main
Juan Leyva 2019-04-30 20:12:57 +02:00 committed by GitHub
commit 980af8d852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 752 additions and 555 deletions

View File

@ -12,10 +12,13 @@
// 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 } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { AddonModDataProvider } from '../../providers/data'; import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline'; import { AddonModDataOfflineProvider } from '../../providers/offline';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { CoreUserProvider } from '@core/user/providers/user'; import { CoreUserProvider } from '@core/user/providers/user';
/** /**
@ -30,6 +33,8 @@ export class AddonModDataActionComponent implements OnInit {
@Input() action: string; // The field to render. @Input() action: string; // The field to render.
@Input() entry?: any; // The value of the field. @Input() entry?: any; // The value of the field.
@Input() database: any; // Database object. @Input() database: any; // Database object.
@Input() module: any; // Module object.
@Input() group: number; // Module object.
@Input() offset?: number; // Offset of the entry. @Input() offset?: number; // Offset of the entry.
siteId: string; siteId: string;
@ -39,11 +44,72 @@ export class AddonModDataActionComponent implements OnInit {
constructor(protected injector: Injector, protected dataProvider: AddonModDataProvider, constructor(protected injector: Injector, protected dataProvider: AddonModDataProvider,
protected dataOffline: AddonModDataOfflineProvider, protected eventsProvider: CoreEventsProvider, protected dataOffline: AddonModDataOfflineProvider, protected eventsProvider: CoreEventsProvider,
sitesProvider: CoreSitesProvider, protected userProvider: CoreUserProvider) { sitesProvider: CoreSitesProvider, protected userProvider: CoreUserProvider, private navCtrl: NavController,
protected linkHelper: CoreContentLinksHelperProvider, private dataHelper: AddonModDataHelperProvider) {
this.rootUrl = sitesProvider.getCurrentSite().getURL(); this.rootUrl = sitesProvider.getCurrentSite().getURL();
this.siteId = sitesProvider.getCurrentSiteId(); this.siteId = sitesProvider.getCurrentSiteId();
} }
/**
* Component being initialized.
*/
ngOnInit(): void {
if (this.action == 'userpicture') {
this.userProvider.getProfile(this.entry.userid, this.database.courseid).then((profile) => {
this.userPicture = profile.profileimageurl;
});
}
}
/**
* Approve the entry.
*/
approveEntry(): void {
this.dataHelper.approveOrDisapproveEntry(this.database.id, this.entry.id, true, this.database.courseid);
}
/**
* Show confirmation modal for deleting the entry.
*/
deleteEntry(): void {
this.dataHelper.showDeleteEntryModal(this.database.id, this.entry.id, this.database.courseid);
}
/**
* Disapprove the entry.
*/
disapproveEntry(): void {
this.dataHelper.approveOrDisapproveEntry(this.database.id, this.entry.id, false, this.database.courseid);
}
/**
* Go to the edit page of the entry.
*/
editEntry(): void {
const pageParams = {
courseId: this.database.course,
module: this.module,
entryId: this.entry.id
};
this.linkHelper.goInSite(this.navCtrl, 'AddonModDataEditPage', pageParams);
}
/**
* Go to the view page of the entry.
*/
viewEntry(): void {
const pageParams: any = {
courseId: this.database.course,
module: this.module,
entryId: this.entry.id,
group: this.group,
offset: this.offset
};
this.linkHelper.goInSite(this.navCtrl, 'AddonModDataEntryPage', pageParams);
}
/** /**
* Undo delete action. * Undo delete action.
* *
@ -60,37 +126,4 @@ export class AddonModDataActionComponent implements OnInit {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId}, this.siteId); this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId}, this.siteId);
}); });
} }
/**
* Component being initialized.
*/
ngOnInit(): void {
switch (this.action) {
case 'more':
this.url = this.rootUrl + '/mod/data/view.php?d= ' + this.entry.dataid + '&rid=' + this.entry.id;
if (typeof this.offset == 'number') {
this.url += '&mode=single&page=' + this.offset;
}
break;
case 'edit':
this.url = this.rootUrl + '/mod/data/edit.php?d= ' + this.entry.dataid + '&rid=' + this.entry.id;
break;
case 'delete':
this.url = this.rootUrl + '/mod/data/view.php?d= ' + this.entry.dataid + '&delete=' + this.entry.id;
break;
case 'approve':
this.url = this.rootUrl + '/mod/data/view.php?d= ' + this.entry.dataid + '&approve=' + this.entry.id;
break;
case 'disapprove':
this.url = this.rootUrl + '/mod/data/view.php?d= ' + this.entry.dataid + '&disapprove=' + this.entry.id;
break;
case 'userpicture':
this.userProvider.getProfile(this.entry.userid, this.database.courseid).then((profile) => {
this.userPicture = profile.profileimageurl;
});
break;
default:
break;
}
}
} }

View File

@ -1,12 +1,12 @@
<a *ngIf="action == 'more'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'addon.mod_data.more' | translate"> <a *ngIf="action == 'more'" ion-button icon-only clear (click)="viewEntry()" [title]="'addon.mod_data.more' | translate">
<ion-icon name="search"></ion-icon> <ion-icon name="search"></ion-icon>
</a> </a>
<a *ngIf="action == 'edit'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'core.edit' | translate"> <a *ngIf="action == 'edit'" ion-button icon-only clear (click)="editEntry()" [title]="'core.edit' | translate">
<ion-icon name="cog"></ion-icon> <ion-icon name="cog"></ion-icon>
</a> </a>
<a *ngIf="action == 'delete' && !entry.deleted" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'core.delete' | translate"> <a *ngIf="action == 'delete' && !entry.deleted" ion-button icon-only clear (click)="deleteEntry()" [title]="'core.delete' | translate">
<ion-icon name="trash"></ion-icon> <ion-icon name="trash"></ion-icon>
</a> </a>
@ -14,11 +14,11 @@
<ion-icon name="undo"></ion-icon> <ion-icon name="undo"></ion-icon>
</a> </a>
<a *ngIf="action == 'approve'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'addon.mod_data.approve' | translate"> <a *ngIf="action == 'approve'" ion-button icon-only clear (click)="approveEntry()" [title]="'addon.mod_data.approve' | translate">
<ion-icon name="thumbs-up"></ion-icon> <ion-icon name="thumbs-up"></ion-icon>
</a> </a>
<a *ngIf="action == 'disapprove'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'addon.mod_data.disapprove' | translate"> <a *ngIf="action == 'disapprove'" ion-button icon-only clear (click)="disapproveEntry()" [title]="'addon.mod_data.disapprove' | translate">
<ion-icon name="thumbs-down"></ion-icon> <ion-icon name="thumbs-down"></ion-icon>
</a> </a>

View File

@ -20,11 +20,9 @@ import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { CoreCommentsProvider } from '@core/comments/providers/comments'; import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { CoreRatingProvider } from '@core/rating/providers/rating'; import { CoreRatingProvider } from '@core/rating/providers/rating';
import { CoreRatingOfflineProvider } from '@core/rating/providers/offline';
import { CoreRatingSyncProvider } from '@core/rating/providers/sync'; import { CoreRatingSyncProvider } from '@core/rating/providers/sync';
import { AddonModDataProvider } from '../../providers/data'; import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper'; import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline';
import { AddonModDataSyncProvider } from '../../providers/sync'; import { AddonModDataSyncProvider } from '../../providers/sync';
import { AddonModDataComponentsModule } from '../components.module'; import { AddonModDataComponentsModule } from '../components.module';
import { AddonModDataPrefetchHandler } from '../../providers/prefetch-handler'; import { AddonModDataPrefetchHandler } from '../../providers/prefetch-handler';
@ -65,8 +63,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
advanced: [] advanced: []
}; };
hasNextPage = false; hasNextPage = false;
offlineActions: any;
offlineEntries: any;
entriesRendered = ''; entriesRendered = '';
extraImports = [AddonModDataComponentsModule]; extraImports = [AddonModDataComponentsModule];
jsData; jsData;
@ -81,12 +77,19 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
protected ratingOfflineObserver: any; protected ratingOfflineObserver: any;
protected ratingSyncObserver: any; protected ratingSyncObserver: any;
constructor(injector: Injector, private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider, constructor(
private dataOffline: AddonModDataOfflineProvider, @Optional() content: Content, injector: Injector,
private prefetchHandler: AddonModDataPrefetchHandler, private timeUtils: CoreTimeUtilsProvider, @Optional() content: Content,
private groupsProvider: CoreGroupsProvider, private commentsProvider: CoreCommentsProvider, private dataProvider: AddonModDataProvider,
private modalCtrl: ModalController, private utils: CoreUtilsProvider, protected navCtrl: NavController, private dataHelper: AddonModDataHelperProvider,
private ratingOffline: CoreRatingOfflineProvider) { private prefetchHandler: AddonModDataPrefetchHandler,
private timeUtils: CoreTimeUtilsProvider,
private groupsProvider: CoreGroupsProvider,
private commentsProvider: CoreCommentsProvider,
private modalCtrl: ModalController,
private utils: CoreUtilsProvider,
protected navCtrl: NavController) {
super(injector, content); super(injector, content);
// Refresh entries on change. // Refresh entries on change.
@ -233,8 +236,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
this.selectedGroup = groupInfo.groups[0].id; this.selectedGroup = groupInfo.groups[0].id;
} }
} }
return this.fetchOfflineEntries();
}); });
}).then(() => { }).then(() => {
return this.dataProvider.getFields(this.data.id).then((fields) => { return this.dataProvider.getFields(this.data.id).then((fields) => {
@ -270,21 +271,19 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
// Update values for current group. // Update values for current group.
this.access.canaddentry = accessData.canaddentry; this.access.canaddentry = accessData.canaddentry;
if (this.search.searching) { const search = this.search.searching && !this.search.searchingAdvanced ? this.search.text : undefined;
const text = this.search.searchingAdvanced ? undefined : this.search.text, const advSearch = this.search.searching && this.search.searchingAdvanced ? this.search.advanced : undefined;
advanced = this.search.searchingAdvanced ? this.search.advanced : undefined;
return this.dataProvider.searchEntries(this.data.id, this.selectedGroup, text, advanced, this.search.sortBy, return this.dataHelper.fetchEntries(this.data, this.fieldsArray, this.selectedGroup, search, advSearch,
this.search.sortDirection, this.search.page); this.search.sortBy, this.search.sortDirection, this.search.page);
} else {
return this.dataProvider.getEntries(this.data.id, this.selectedGroup, this.search.sortBy, this.search.sortDirection,
this.search.page);
}
}).then((entries) => { }).then((entries) => {
const numEntries = (entries && entries.entries && entries.entries.length) || 0; const numEntries = entries.entries.length;
this.isEmpty = !numEntries && !Object.keys(this.offlineActions).length && !Object.keys(this.offlineEntries).length; const numOfflineEntries = entries.offlineEntries.length;
this.isEmpty = !numEntries && !entries.offlineEntries.length;
this.hasNextPage = numEntries >= AddonModDataProvider.PER_PAGE && ((this.search.page + 1) * this.hasNextPage = numEntries >= AddonModDataProvider.PER_PAGE && ((this.search.page + 1) *
AddonModDataProvider.PER_PAGE) < entries.totalcount; AddonModDataProvider.PER_PAGE) < entries.totalcount;
this.hasOffline = entries.hasOfflineActions;
this.hasOfflineRatings = entries.hasOfflineRatings;
this.entriesRendered = ''; this.entriesRendered = '';
if (typeof entries.maxcount != 'undefined') { if (typeof entries.maxcount != 'undefined') {
@ -298,79 +297,40 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
} }
if (!this.isEmpty) { if (!this.isEmpty) {
const siteInfo = this.sitesProvider.getCurrentSite().getInfo(), this.entries = entries.offlineEntries.concat(entries.entries);
promises = [];
this.utils.objectToArray(this.offlineEntries).forEach((offlineActions) => { let entriesHTML = this.data.listtemplateheader || '';
const offlineEntry = offlineActions.find((offlineEntry) => offlineEntry.action == 'add');
if (offlineEntry) { // Get first entry from the whole list.
const entry = { if (!this.search.searching || !this.firstEntry) {
id: offlineEntry.entryid, this.firstEntry = this.entries[0].id;
canmanageentry: true, }
approved: !this.data.approval || this.data.manageapproved,
dataid: offlineEntry.dataid,
groupid: offlineEntry.groupid,
timecreated: -offlineEntry.entryid,
timemodified: -offlineEntry.entryid,
userid: siteInfo.userid,
fullname: siteInfo.fullname,
contents: {}
};
if (offlineActions.length > 0) { const template = this.data.listtemplate || this.dataHelper.getDefaultTemplate('list', this.fieldsArray);
promises.push(this.dataHelper.applyOfflineActions(entry, offlineActions, this.fieldsArray));
} else { const entriesById = {};
promises.push(Promise.resolve(entry)); this.entries.forEach((entry, index) => {
} entriesById[entry.id] = entry;
}
const actions = this.dataHelper.getActions(this.data, this.access, entry);
const offset = this.search.searching ? undefined :
this.search.page * AddonModDataProvider.PER_PAGE + index - numOfflineEntries;
entriesHTML += this.dataHelper.displayShowFields(template, this.fieldsArray, entry, offset, 'list', actions);
}); });
entriesHTML += this.data.listtemplatefooter || '';
entries.entries.forEach((entry) => { this.entriesRendered = entriesHTML;
// Index contents by fieldid.
entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
if (typeof this.offlineActions[entry.id] != 'undefined') { // Pass the input data to the component.
promises.push(this.dataHelper.applyOfflineActions(entry, this.offlineActions[entry.id], this.fieldsArray)); this.jsData = {
} else { fields: this.fields,
promises.push(Promise.resolve(entry)); entries: entriesById,
} data: this.data,
}); module: this.module,
group: this.selectedGroup,
return Promise.all(promises).then((entries) => { gotoEntry: this.gotoEntry.bind(this)
this.entries = entries; };
let entriesHTML = this.data.listtemplateheader || '';
// Get first entry from the whole list.
if (entries && entries[0] && (!this.search.searching || !this.firstEntry)) {
this.firstEntry = entries[0].id;
}
const template = this.data.listtemplate || this.dataHelper.getDefaultTemplate('list', this.fieldsArray);
const entriesById = {};
entries.forEach((entry, index) => {
entriesById[entry.id] = entry;
const actions = this.dataHelper.getActions(this.data, this.access, entry);
const offset = this.search.page * AddonModDataProvider.PER_PAGE + index;
entriesHTML += this.dataHelper.displayShowFields(template, this.fieldsArray, entry, offset, 'list',
actions);
});
entriesHTML += this.data.listtemplatefooter || '';
this.entriesRendered = entriesHTML;
// Pass the input data to the component.
this.jsData = {
fields: this.fields,
entries: entriesById,
data: this.data,
gotoEntry: this.gotoEntry.bind(this)
};
});
} else if (!this.search.searching) { } else if (!this.search.searching) {
// Empty and no searching. // Empty and no searching.
this.canSearch = false; this.canSearch = false;
@ -435,6 +395,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
*/ */
setGroup(groupId: number): Promise<any> { setGroup(groupId: number): Promise<any> {
this.selectedGroup = groupId; this.selectedGroup = groupId;
this.search.page = 0;
return this.fetchEntriesData().catch((message) => { return this.fetchEntriesData().catch((message) => {
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
@ -479,42 +440,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
this.navCtrl.push('AddonModDataEntryPage', params); this.navCtrl.push('AddonModDataEntryPage', params);
} }
/**
* Fetch offline entries.
*
* @return {Promise<any>} Resolved then done.
*/
protected fetchOfflineEntries(): Promise<any> {
// Check if there are entries stored in offline.
return this.dataOffline.getDatabaseEntries(this.data.id).then((offlineEntries) => {
this.hasOffline = !!offlineEntries.length;
this.offlineActions = {};
this.offlineEntries = {};
// Only show offline entries on first page.
if (this.search.page == 0 && this.hasOffline) {
offlineEntries.forEach((entry) => {
if (entry.entryid > 0) {
if (typeof this.offlineActions[entry.entryid] == 'undefined') {
this.offlineActions[entry.entryid] = [];
}
this.offlineActions[entry.entryid].push(entry);
} else {
if (typeof this.offlineActions[entry.entryid] == 'undefined') {
this.offlineEntries[entry.entryid] = [];
}
this.offlineEntries[entry.entryid].push(entry);
}
});
}
}).then(() => {
return this.ratingOffline.hasRatings('mod_data', 'entry', 'module', this.data.coursemodule).then((hasRatings) => {
this.hasOfflineRatings = hasRatings;
});
});
}
/** /**
* Performs the sync of the activity. * Performs the sync of the activity.
* *

View File

@ -45,7 +45,6 @@ export class AddonModDataEditPage {
protected data: any; protected data: any;
protected entryId: number; protected entryId: number;
protected entry: any; protected entry: any;
protected offlineActions = [];
protected fields = {}; protected fields = {};
protected fieldsArray = []; protected fieldsArray = [];
protected siteId: string; protected siteId: string;
@ -145,31 +144,14 @@ export class AddonModDataEditPage {
}); });
} }
}).then(() => { }).then(() => {
return this.dataOffline.getEntryActions(this.data.id, this.entryId);
}).then((actions) => {
this.offlineActions = actions;
return this.dataProvider.getFields(this.data.id); return this.dataProvider.getFields(this.data.id);
}).then((fieldsData) => { }).then((fieldsData) => {
this.fieldsArray = fieldsData; this.fieldsArray = fieldsData;
this.fields = this.utils.arrayToObject(fieldsData, 'id'); this.fields = this.utils.arrayToObject(fieldsData, 'id');
return this.dataHelper.getEntry(this.data, this.entryId, this.offlineActions); return this.dataHelper.fetchEntry(this.data, fieldsData, this.entryId);
}).then((entry) => { }).then((entry) => {
if (entry) { this.entry = entry.entry;
entry = entry.entry;
// Index contents by fieldid.
entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
} else {
entry = {
contents: {}
};
}
return this.dataHelper.applyOfflineActions(entry, this.offlineActions, this.fieldsArray);
}).then((entryData) => {
this.entry = entryData;
this.editFormRender = this.displayEditFields(); this.editFormRender = this.displayEditFields();
}).catch((message) => { }).catch((message) => {

View File

@ -9,7 +9,7 @@
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="entryLoaded && (isPullingToRefresh || !renderingEntry && !loadingRating && !loadingComments)"> <core-loading [hideUntil]="entryLoaded && (isPullingToRefresh || !renderingEntry && !loadingRating && !loadingComments)">
<!-- Database entries found to be synchronized --> <!-- Database entries found to be synchronized -->
<div class="core-warning-card" icon-start *ngIf="hasOffline"> <div class="core-warning-card" icon-start *ngIf="entry && entry.hasOffline">
<ion-icon name="warning"></ion-icon> <ion-icon name="warning"></ion-icon>
{{ 'core.hasdatatosync' | translate: {$a: moduleName} }} {{ 'core.hasdatatosync' | translate: {$a: moduleName} }}
</div> </div>
@ -31,7 +31,7 @@
<core-rating-rate *ngIf="data && entry && ratingInfo && (!data.approval || entry.approved)" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="data.coursemodule" [itemId]="entry.id" [itemSetId]="0" [courseId]="courseId" [aggregateMethod]="data.assessed" [scaleId]="data.scale" [userId]="entry.userid" (onLoading)="setLoadingRating($event)" (onUpdate)="ratingUpdated()"></core-rating-rate> <core-rating-rate *ngIf="data && entry && ratingInfo && (!data.approval || entry.approved)" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="data.coursemodule" [itemId]="entry.id" [itemSetId]="0" [courseId]="courseId" [aggregateMethod]="data.assessed" [scaleId]="data.scale" [userId]="entry.userid" (onLoading)="setLoadingRating($event)" (onUpdate)="ratingUpdated()"></core-rating-rate>
<core-rating-aggregate *ngIf="data && entry && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="data.coursemodule" [itemId]="entry.id" [courseId]="courseId" [aggregateMethod]="data.assessed" [scaleId]="data.scale"></core-rating-aggregate> <core-rating-aggregate *ngIf="data && entry && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="data.coursemodule" [itemId]="entry.id" [courseId]="courseId" [aggregateMethod]="data.assessed" [scaleId]="data.scale"></core-rating-aggregate>
<ion-item *ngIf="data && entry"> <ion-item *ngIf="data && entry && entry.id > 0">
<core-comments contextLevel="module" [instanceId]="data.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry" [displaySpinner]="false" (onLoading)="setLoadingComments($event)"></core-comments> <core-comments contextLevel="module" [instanceId]="data.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry" [displaySpinner]="false" (onLoading)="setLoadingComments($event)"></core-comments>
</ion-item> </ion-item>

View File

@ -23,7 +23,6 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreRatingInfo } from '@core/rating/providers/rating'; import { CoreRatingInfo } from '@core/rating/providers/rating';
import { AddonModDataProvider } from '../../providers/data'; import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper'; import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline';
import { AddonModDataSyncProvider } from '../../providers/sync'; import { AddonModDataSyncProvider } from '../../providers/sync';
import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate'; import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
import { AddonModDataComponentsModule } from '../../components/components.module'; import { AddonModDataComponentsModule } from '../../components/components.module';
@ -46,6 +45,7 @@ export class AddonModDataEntryPage implements OnDestroy {
protected syncObserver: any; // It will observe the sync auto event. protected syncObserver: any; // It will observe the sync auto event.
protected entryChangedObserver: any; // It will observe the changed entry event. protected entryChangedObserver: any; // It will observe the changed entry event.
protected fields = {}; protected fields = {};
protected fieldsArray = [];
title = ''; title = '';
moduleName = 'data'; moduleName = 'data';
@ -56,8 +56,6 @@ export class AddonModDataEntryPage implements OnDestroy {
loadingRating = false; loadingRating = false;
selectedGroup = 0; selectedGroup = 0;
entry: any; entry: any;
offlineActions = [];
hasOffline = false;
previousOffset: number; previousOffset: number;
nextOffset: number; nextOffset: number;
access: any; access: any;
@ -74,7 +72,7 @@ export class AddonModDataEntryPage implements OnDestroy {
constructor(params: NavParams, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider, constructor(params: NavParams, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider,
protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate, protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
protected courseProvider: CoreCourseProvider, protected dataProvider: AddonModDataProvider, protected courseProvider: CoreCourseProvider, protected dataProvider: AddonModDataProvider,
protected dataOffline: AddonModDataOfflineProvider, protected dataHelper: AddonModDataHelperProvider, protected dataHelper: AddonModDataHelperProvider,
sitesProvider: CoreSitesProvider, protected navCtrl: NavController, protected eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, protected navCtrl: NavController, protected eventsProvider: CoreEventsProvider,
private cdr: ChangeDetectorRef) { private cdr: ChangeDetectorRef) {
this.module = params.get('module') || {}; this.module = params.get('module') || {};
@ -131,16 +129,19 @@ export class AddonModDataEntryPage implements OnDestroy {
* @return {Promise<any>} Resolved when done. * @return {Promise<any>} Resolved when done.
*/ */
protected fetchEntryData(refresh?: boolean, isPtr?: boolean): Promise<any> { protected fetchEntryData(refresh?: boolean, isPtr?: boolean): Promise<any> {
let fieldsArray;
this.isPullingToRefresh = isPtr; this.isPullingToRefresh = isPtr;
return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => {
this.title = data.name || this.title; this.title = data.name || this.title;
this.data = data; this.data = data;
return this.setEntryIdFromOffset(data.id, this.offset, this.selectedGroup).then(() => { return this.dataProvider.getFields(this.data.id).then((fieldsData) => {
return this.dataProvider.getDatabaseAccessInformation(data.id); this.fields = this.utils.arrayToObject(fieldsData, 'id');
this.fieldsArray = fieldsData;
});
}).then(() => {
return this.setEntryFromOffset().then(() => {
return this.dataProvider.getDatabaseAccessInformation(this.data.id);
}); });
}).then((accessData) => { }).then((accessData) => {
this.access = accessData; this.access = accessData;
@ -155,35 +156,13 @@ export class AddonModDataEntryPage implements OnDestroy {
this.selectedGroup = groupInfo.groups[0].id; this.selectedGroup = groupInfo.groups[0].id;
} }
} }
return this.dataOffline.getEntryActions(this.data.id, this.entryId);
}); });
}).then((actions) => { }).then(() => {
this.offlineActions = actions;
this.hasOffline = !!actions.length;
return this.dataProvider.getFields(this.data.id).then((fieldsData) => {
this.fields = this.utils.arrayToObject(fieldsData, 'id');
return this.dataHelper.getEntry(this.data, this.entryId, this.offlineActions);
});
}).then((entry) => {
this.ratingInfo = entry.ratinginfo;
entry = entry.entry;
// Index contents by fieldid.
entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
fieldsArray = this.utils.objectToArray(this.fields);
return this.dataHelper.applyOfflineActions(entry, this.offlineActions, fieldsArray);
}).then((entryData) => {
this.entry = entryData;
const actions = this.dataHelper.getActions(this.data, this.access, this.entry); const actions = this.dataHelper.getActions(this.data, this.access, this.entry);
const templte = this.data.singletemplate || this.dataHelper.getDefaultTemplate('single', fieldsArray); const template = this.data.singletemplate || this.dataHelper.getDefaultTemplate('single', this.fieldsArray);
this.entryHtml = this.dataHelper.displayShowFields(templte, fieldsArray, this.entry, this.offset, 'show', actions); this.entryHtml = this.dataHelper.displayShowFields(template, this.fieldsArray, this.entry, this.offset, 'show',
actions);
this.showComments = actions.comments; this.showComments = actions.comments;
const entries = {}; const entries = {};
@ -193,7 +172,9 @@ export class AddonModDataEntryPage implements OnDestroy {
this.jsData = { this.jsData = {
fields: this.fields, fields: this.fields,
entries: entries, entries: entries,
data: this.data data: this.data,
module: this.module,
group: this.selectedGroup
}; };
}).catch((message) => { }).catch((message) => {
if (!refresh) { if (!refresh) {
@ -266,7 +247,7 @@ export class AddonModDataEntryPage implements OnDestroy {
*/ */
setGroup(groupId: number): Promise<any> { setGroup(groupId: number): Promise<any> {
this.selectedGroup = groupId; this.selectedGroup = groupId;
this.offset = 0; this.offset = null;
this.entry = null; this.entry = null;
this.entryId = null; this.entryId = null;
this.entryLoaded = false; this.entryLoaded = false;
@ -275,46 +256,73 @@ export class AddonModDataEntryPage implements OnDestroy {
} }
/** /**
* Convenience function to translate offset to entry identifier and set next/previous entries. * Convenience function to fetch the entry and set next/previous entries.
* *
* @param {number} dataId Data Id.
* @param {number} [offset] Offset of the entry.
* @param {number} [groupId] Group Id to get the entry.
* @return {Promise<any>} Resolved when done. * @return {Promise<any>} Resolved when done.
*/ */
protected setEntryIdFromOffset(dataId: number, offset?: number, groupId?: number): Promise<any> { protected setEntryFromOffset(): Promise<any> {
if (typeof offset != 'number') { const emptyOffset = typeof this.offset != 'number';
if (emptyOffset && typeof this.entryId == 'number') {
// Entry id passed as navigation parameter instead of the offset. // Entry id passed as navigation parameter instead of the offset.
// We don't display next/previous buttons in this case. // We don't display next/previous buttons in this case.
this.nextOffset = null; this.nextOffset = null;
this.previousOffset = null; this.previousOffset = null;
return Promise.resolve(); return this.dataHelper.fetchEntry(this.data, this.fieldsArray, this.entryId).then((entry) => {
this.entry = entry.entry;
this.ratingInfo = entry.ratinginfo;
});
} }
const perPage = AddonModDataProvider.PER_PAGE; const perPage = AddonModDataProvider.PER_PAGE;
const page = Math.floor(offset / perPage); const page = !emptyOffset && this.offset >= 0 ? Math.floor(this.offset / perPage) : 0;
const pageOffset = offset % perPage;
return this.dataProvider.getEntries(dataId, groupId, undefined, undefined, page, perPage).then((entries) => { return this.dataHelper.fetchEntries(this.data, this.fieldsArray, this.selectedGroup, undefined, undefined, '0', 'DESC',
if (!entries || !entries.entries || !entries.entries.length || pageOffset >= entries.entries.length) { page, perPage).then((entries) => {
return Promise.reject(null);
const pageEntries = entries.offlineEntries.concat(entries.entries);
let pageIndex; // Index of the entry when concatenating offline and online page entries.
if (emptyOffset) {
// No offset passed, display the first entry.
pageIndex = 0;
} else if (this.offset > 0) {
// Online entry.
pageIndex = this.offset % perPage + entries.offlineEntries.length;
} else {
// Offline entry.
pageIndex = this.offset + entries.offlineEntries.length;
} }
this.entryId = entries.entries[pageOffset].id; this.entry = pageEntries[pageIndex];
this.previousOffset = offset > 0 ? offset - 1 : null; this.entryId = this.entry.id;
if (pageOffset + 1 < entries.entries.length) {
this.previousOffset = page > 0 || pageIndex > 0 ? this.offset - 1 : null;
let promise;
if (pageIndex + 1 < pageEntries.length) {
// Not the last entry on the page; // Not the last entry on the page;
this.nextOffset = offset + 1; this.nextOffset = this.offset + 1;
} else if (entries.entries.length < perPage) { } else if (pageEntries.length < perPage) {
// Last entry of the last page. // Last entry of the last page.
this.nextOffset = null; this.nextOffset = null;
} else { } else {
// Last entry of the page, check if there are more pages. // Last entry of the page, check if there are more pages.
return this.dataProvider.getEntries(dataId, groupId, undefined, undefined, page + 1, perPage).then((entries) => { promise = this.dataProvider.getEntries(this.data.id, this.selectedGroup, '0', 'DESC', page + 1, perPage)
this.nextOffset = entries && entries.entries && entries.entries.length > 0 ? offset + 1 : null; .then((entries) => {
this.nextOffset = entries && entries.entries && entries.entries.length > 0 ? this.offset + 1 : null;
}); });
} }
return Promise.resolve(promise).then(() => {
if (this.entryId > 0) {
// Online entry, we need to fetch the the rating info.
return this.dataProvider.getEntry(this.data.id, this.entryId).then((entry) => {
this.ratingInfo = entry.ratinginfo;
});
}
});
}); });
} }

View File

@ -16,9 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { AddonModDataProvider } from './data'; import { AddonModDataProvider } from './data';
import { CoreCourseProvider } from '@core/course/providers/course'; import { AddonModDataHelperProvider } from './helper';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreEventsProvider } from '@providers/events';
/** /**
* Content links handler for database approve/disapprove entry. * Content links handler for database approve/disapprove entry.
@ -30,29 +28,10 @@ export class AddonModDataApproveLinkHandler extends CoreContentLinksHandlerBase
featureName = 'CoreCourseModuleDelegate_AddonModData'; featureName = 'CoreCourseModuleDelegate_AddonModData';
pattern = /\/mod\/data\/view\.php.*([\?\&](d|approve|disapprove)=\d+)/; pattern = /\/mod\/data\/view\.php.*([\?\&](d|approve|disapprove)=\d+)/;
constructor(private dataProvider: AddonModDataProvider, private courseProvider: CoreCourseProvider, constructor(private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider) {
private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider) {
super(); super();
} }
/**
* Convenience function to help get courseId.
*
* @param {number} dataId Database Id.
* @param {string} siteId Site Id, if not set, current site will be used.
* @param {number} courseId Course Id if already set.
* @return {Promise<number>} Resolved with course Id when done.
*/
protected getActivityCourseIdIfNotSet(dataId: number, siteId: string, courseId: number): Promise<number> {
if (courseId) {
return Promise.resolve(courseId);
}
return this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => {
return module.course;
});
}
/** /**
* Get the list of actions for a link (url). * Get the list of actions for a link (url).
* *
@ -66,34 +45,11 @@ export class AddonModDataApproveLinkHandler extends CoreContentLinksHandlerBase
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{ return [{
action: (siteId, navCtrl?): void => { action: (siteId, navCtrl?): void => {
const modal = this.domUtils.showModalLoading(), const dataId = parseInt(params.d, 10),
dataId = parseInt(params.d, 10),
entryId = parseInt(params.approve, 10) || parseInt(params.disapprove, 10), entryId = parseInt(params.approve, 10) || parseInt(params.disapprove, 10),
approve = parseInt(params.approve, 10) ? true : false; approve = parseInt(params.approve, 10) ? true : false;
this.getActivityCourseIdIfNotSet(dataId, siteId, courseId).then((cId) => { this.dataHelper.approveOrDisapproveEntry(dataId, entryId, approve, courseId, siteId);
courseId = cId;
// Approve/disapprove entry.
return this.dataProvider.approveEntry(dataId, entryId, approve, courseId, siteId).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errorapproving', true);
return Promise.reject(null);
});
}).then(() => {
const promises = [];
promises.push(this.dataProvider.invalidateEntryData(dataId, entryId, siteId));
promises.push(this.dataProvider.invalidateEntriesData(dataId, siteId));
return Promise.all(promises);
}).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId}, siteId);
this.domUtils.showToast(approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', true,
3000);
}).finally(() => {
modal.dismiss();
});
} }
}]; }];
} }

View File

@ -21,6 +21,67 @@ import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { AddonModDataOfflineProvider } from './offline'; import { AddonModDataOfflineProvider } from './offline';
import { AddonModDataFieldsDelegate } from './fields-delegate'; import { AddonModDataFieldsDelegate } from './fields-delegate';
import { CoreRatingInfo } from '@core/rating/providers/rating';
/**
* Database entry (online or offline).
*/
export interface AddonModDataEntry {
id: number; // Negative for offline entries.
userid: number;
groupid: number;
dataid: number;
timecreated: number;
timemodified: number;
approved: boolean;
canmanageentry: boolean;
fullname: string;
contents: AddonModDataEntryFields;
deleted?: boolean; // Entry is deleted offline.
hasOffline?: boolean; // Entry has offline actions.
}
/**
* Entry field content.
*/
export interface AddonModDataEntryField {
fieldid: number;
content: string;
content1: string;
content2: string;
content3: string;
content4: string;
files: any[];
}
/**
* Entry contents indexed by field id.
*/
export interface AddonModDataEntryFields {
[fieldid: number]: AddonModDataEntryField;
}
/**
* List of entries returned by web service and helper functions.
*/
export interface AddonModDataEntries {
entries: AddonModDataEntry[]; // Online entries.
totalcount: number; // Total count of online entries or found entries.
maxcount?: number; // Total count of online entries. Only returned when searching.
offlineEntries?: AddonModDataEntry[]; // Offline entries.
hasOfflineActions?: boolean; // Whether the database has offline data.
hasOfflineRatings?: boolean; // Whether the database has offline ratings.
}
/**
* Subfield form data.
*/
export interface AddonModDataSubfieldData {
fieldid: number;
subfield?: string;
value?: string; // Value encoded in JSON.
files?: any[];
}
/** /**
* Service that provides some features for databases. * Service that provides some features for databases.
@ -49,13 +110,13 @@ export class AddonModDataProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {any} contents The fields data to be created. * @param {any} contents The fields data to be created.
* @param {number} [groupId] Group id, 0 means that the function will determine the user group. * @param {number} [groupId] Group id, 0 means that the function will determine the user group.
* @param {any} fields The fields that define the contents. * @param {any[]} fields The fields that define the contents.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceOffline] Force editing entry in offline. * @param {boolean} [forceOffline] Force editing entry in offline.
* @return {Promise<any>} Promise resolved when the action is done. * @return {Promise<any>} Promise resolved when the action is done.
*/ */
addEntry(dataId: number, entryId: number, courseId: number, contents: any, groupId: number = 0, fields: any, siteId?: string, addEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], groupId: number = 0,
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.
@ -76,6 +137,8 @@ export class AddonModDataProvider {
fieldnotifications: notifications fieldnotifications: notifications
}); });
} }
return storeOffline();
} }
return this.addEntryOnline(dataId, contents, groupId, siteId).catch((error) => { return this.addEntryOnline(dataId, contents, groupId, siteId).catch((error) => {
@ -93,12 +156,12 @@ export class AddonModDataProvider {
* Adds a new entry to a database. It does not cache calls. It will fail if offline or cannot connect. * Adds a new entry to a database. It does not cache calls. It will fail if offline or cannot connect.
* *
* @param {number} dataId Database ID. * @param {number} dataId Database ID.
* @param {any} data The fields data to be created. * @param {any[]} data The fields data to be created.
* @param {number} [groupId] Group id, 0 means that the function will determine the user group. * @param {number} [groupId] Group id, 0 means that the function will determine the user group.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the action is done. * @return {Promise<any>} Promise resolved when the action is done.
*/ */
addEntryOnline(dataId: number, data: any, groupId?: number, siteId?: string): Promise<any> { addEntryOnline(dataId: number, data: AddonModDataSubfieldData[], groupId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
databaseid: dataId, databaseid: dataId,
@ -184,7 +247,7 @@ export class AddonModDataProvider {
* @param {any} contents The contents data of the fields. * @param {any} contents The contents data of the fields.
* @return {any} Array of notifications if any or false. * @return {any} Array of notifications if any or false.
*/ */
protected checkFields(fields: any, contents: any): any { protected checkFields(fields: any, contents: AddonModDataSubfieldData[]): any[] | false {
const notifications = [], const notifications = [],
contentsIndexed = {}; contentsIndexed = {};
@ -289,13 +352,13 @@ export class AddonModDataProvider {
* @param {number} dataId Database ID. * @param {number} dataId Database ID.
* @param {number} entryId Entry ID. * @param {number} entryId Entry ID.
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {any} contents The contents data to be updated. * @param {any[]} contents The contents data to be updated.
* @param {any} fields The fields that define the contents. * @param {any} fields The fields that define the contents.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} forceOffline Force editing entry in offline. * @param {boolean} forceOffline Force editing entry in offline.
* @return {Promise<any>} Promise resolved when the action is done. * @return {Promise<any>} Promise resolved when the action is done.
*/ */
editEntry(dataId: number, entryId: number, courseId: number, contents: any, fields: any, siteId?: string, editEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], fields: any, siteId?: string,
forceOffline: boolean = false): Promise<any> { forceOffline: boolean = false): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
@ -370,11 +433,11 @@ export class AddonModDataProvider {
* 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.
* *
* @param {number} entryId Entry ID. * @param {number} entryId Entry ID.
* @param {any} data The fields data to be updated. * @param {any[]} data The fields data to be updated.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the action is done. * @return {Promise<any>} Promise resolved when the action is done.
*/ */
editEntryOnline(entryId: number, data: number, siteId?: string): Promise<any> { editEntryOnline(entryId: number, data: AddonModDataSubfieldData[], siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
entryid: entryId, entryid: entryId,
@ -397,11 +460,11 @@ export class AddonModDataProvider {
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<AddonModDataEntry[]>} Promise resolved when done.
*/ */
fetchAllEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC', fetchAllEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC',
perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false, perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false,
siteId?: string): Promise<any> { siteId?: string): Promise<AddonModDataEntry[]> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.fetchEntriesRecursive(dataId, groupId, sort, order, perPage, forceCache, ignoreCache, [], 0, siteId); return this.fetchEntriesRecursive(dataId, groupId, sort, order, perPage, forceCache, ignoreCache, [], 0, siteId);
@ -420,10 +483,10 @@ export class AddonModDataProvider {
* @param {any} entries Entries already fetch (just to concatenate them). * @param {any} entries Entries already fetch (just to concatenate them).
* @param {number} page Page of records to return. * @param {number} page Page of records to return.
* @param {string} siteId Site ID. * @param {string} siteId Site ID.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<AddonModDataEntry[]>} Promise resolved when done.
*/ */
protected fetchEntriesRecursive(dataId: number, groupId: number, sort: string, order: string, perPage: number, protected fetchEntriesRecursive(dataId: number, groupId: number, sort: string, order: string, perPage: number,
forceCache: boolean, ignoreCache: boolean, entries: any, page: number, siteId: string): Promise<any> { forceCache: boolean, ignoreCache: boolean, entries: any, page: number, siteId: string): Promise<AddonModDataEntry[]> {
return this.getEntries(dataId, groupId, sort, order, page, perPage, forceCache, ignoreCache, siteId) return this.getEntries(dataId, groupId, sort, order, page, perPage, forceCache, ignoreCache, siteId)
.then((result) => { .then((result) => {
entries = entries.concat(result.entries); entries = entries.concat(result.entries);
@ -595,11 +658,11 @@ export class AddonModDataProvider {
* @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down). * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the database is retrieved. * @return {Promise<AddonModDataEntries>} Promise resolved when the database is retrieved.
*/ */
getEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC', page: number = 0, getEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC', page: number = 0,
perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false, perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false,
siteId?: string): Promise<any> { siteId?: string): Promise<AddonModDataEntries> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
// Always use sort and order params to improve cache usage (entries are identified by params). // Always use sort and order params to improve cache usage (entries are identified by params).
const params = { const params = {
@ -622,7 +685,13 @@ export class AddonModDataProvider {
preSets['emergencyCache'] = false; preSets['emergencyCache'] = false;
} }
return site.read('mod_data_get_entries', params, preSets); return site.read('mod_data_get_entries', params, preSets).then((response) => {
response.entries.forEach((entry) => {
entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
});
return response;
});
}); });
} }
@ -654,9 +723,10 @@ export class AddonModDataProvider {
* @param {number} entryId Entry ID. * @param {number} entryId Entry ID.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down). * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the database entry is retrieved. * @return {Promise<{entry: AddonModDataEntry, ratinginfo: CoreRatingInfo}>} Promise resolved when the entry is retrieved.
*/ */
getEntry(dataId: number, entryId: number, ignoreCache: boolean = false, siteId?: string): Promise<any> { getEntry(dataId: number, entryId: number, ignoreCache: boolean = false, siteId?: string):
Promise<{entry: AddonModDataEntry, ratinginfo: CoreRatingInfo}> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
entryid: entryId, entryid: entryId,
@ -671,7 +741,11 @@ export class AddonModDataProvider {
preSets['emergencyCache'] = false; preSets['emergencyCache'] = false;
} }
return site.read('mod_data_get_entry', params, preSets); return site.read('mod_data_get_entry', params, preSets).then((response) => {
response.entry.contents = this.utils.arrayToObject(response.entry.contents, 'fieldid');
return response;
});
}); });
} }
@ -871,16 +945,16 @@ export class AddonModDataProvider {
* @param {number} dataId The data instance id. * @param {number} dataId The data instance id.
* @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group.
* @param {string} [search] Search text. It will be used if advSearch is not defined. * @param {string} [search] Search text. It will be used if advSearch is not defined.
* @param {any} [advSearch] Advanced search data. * @param {any[]} [advSearch] Advanced search data.
* @param {string} [sort] Sort by this field. * @param {string} [sort] Sort by this field.
* @param {string} [order] The direction of the sorting. * @param {string} [order] The direction of the sorting.
* @param {number} [page=0] Page of records to return. * @param {number} [page=0] Page of records to return.
* @param {number} [perPage=PER_PAGE] Records per page to return. Default on AddonModDataProvider.PER_PAGE. * @param {number} [perPage=PER_PAGE] Records per page to return. Default on AddonModDataProvider.PER_PAGE.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the action is done. * @return {Promise<AddonModDataEntries>} Promise resolved when the action is done.
*/ */
searchEntries(dataId: number, groupId: number = 0, search?: string, advSearch?: any, sort?: string, order?: string, searchEntries(dataId: number, groupId: number = 0, search?: string, advSearch?: any, sort?: string, order?: string,
page: number = 0, perPage: number = AddonModDataProvider.PER_PAGE, siteId?: string): Promise<any> { page: number = 0, perPage: number = AddonModDataProvider.PER_PAGE, siteId?: string): Promise<AddonModDataEntries> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
databaseid: dataId, databaseid: dataId,
@ -911,7 +985,13 @@ export class AddonModDataProvider {
params['advsearch'] = advSearch; params['advsearch'] = advSearch;
} }
return site.read('mod_data_search_entries', params, preSets); return site.read('mod_data_search_entries', params, preSets).then((response) => {
response.entries.forEach((entry) => {
entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
});
return response;
});
}); });
} }
} }

View File

@ -13,13 +13,10 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { AddonModDataProvider } from './data'; import { AddonModDataProvider } from './data';
import { CoreCourseProvider } from '@core/course/providers/course'; import { AddonModDataHelperProvider } from './helper';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreEventsProvider } from '@providers/events';
/** /**
* Content links handler for database delete entry. * Content links handler for database delete entry.
@ -31,30 +28,10 @@ export class AddonModDataDeleteLinkHandler extends CoreContentLinksHandlerBase {
featureName = 'CoreCourseModuleDelegate_AddonModData'; featureName = 'CoreCourseModuleDelegate_AddonModData';
pattern = /\/mod\/data\/view\.php.*([\?\&](d|delete)=\d+)/; pattern = /\/mod\/data\/view\.php.*([\?\&](d|delete)=\d+)/;
constructor(private dataProvider: AddonModDataProvider, private courseProvider: CoreCourseProvider, constructor(private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider) {
private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider,
private translate: TranslateService) {
super(); super();
} }
/**
* Convenience function to help get courseId.
*
* @param {number} dataId Database Id.
* @param {string} siteId Site Id, if not set, current site will be used.
* @param {number} courseId Course Id if already set.
* @return {Promise<number>} Resolved with course Id when done.
*/
protected getActivityCourseIdIfNotSet(dataId: number, siteId: string, courseId: number): Promise<number> {
if (courseId) {
return Promise.resolve(courseId);
}
return this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => {
return module.course;
});
}
/** /**
* Get the list of actions for a link (url). * Get the list of actions for a link (url).
* *
@ -68,38 +45,10 @@ export class AddonModDataDeleteLinkHandler extends CoreContentLinksHandlerBase {
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{ return [{
action: (siteId, navCtrl?): void => { action: (siteId, navCtrl?): void => {
const dataId = parseInt(params.d, 10);
const entryId = parseInt(params.delete, 10);
this.domUtils.showConfirm(this.translate.instant('addon.mod_data.confirmdeleterecord')).then(() => { this.dataHelper.showDeleteEntryModal(dataId, entryId, courseId);
const modal = this.domUtils.showModalLoading(),
dataId = parseInt(params.d, 10),
entryId = parseInt(params.delete, 10);
return this.getActivityCourseIdIfNotSet(dataId, siteId, courseId).then((cId) => {
courseId = cId;
// Delete entry.
return this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true);
return Promise.reject(null);
});
}).then(() => {
const promises = [];
promises.push(this.dataProvider.invalidateEntryData(dataId, entryId, siteId));
promises.push(this.dataProvider.invalidateEntriesData(dataId, siteId));
return Promise.all(promises);
}).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId,
deleted: true}, siteId);
this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000);
}).finally(() => {
modal.dismiss();
});
}).catch(() => {
// Nothing to do.
});
} }
}]; }];
} }

View File

@ -14,12 +14,18 @@
import { Injectable } from '@angular/core'; import { Injectable } 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 { CoreSitesProvider } from '@providers/sites';
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 { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { AddonModDataFieldsDelegate } from './fields-delegate'; import { AddonModDataFieldsDelegate } from './fields-delegate';
import { AddonModDataOfflineProvider } from './offline'; import { AddonModDataOfflineProvider, AddonModDataOfflineAction } from './offline';
import { AddonModDataProvider } from './data'; import { AddonModDataProvider, AddonModDataEntry, AddonModDataEntryFields, AddonModDataEntries } from './data';
import { CoreRatingInfo } from '@core/rating/providers/rating';
import { CoreRatingOfflineProvider } from '@core/rating/providers/offline';
/** /**
* Service that provides helper functions for datas. * Service that provides helper functions for datas.
@ -30,20 +36,26 @@ export class AddonModDataHelperProvider {
constructor(private sitesProvider: CoreSitesProvider, protected dataProvider: AddonModDataProvider, constructor(private sitesProvider: CoreSitesProvider, protected dataProvider: AddonModDataProvider,
private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate, private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate,
private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider, private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider,
private textUtils: CoreTextUtilsProvider) { } private textUtils: CoreTextUtilsProvider, private eventsProvider: CoreEventsProvider, private utils: CoreUtilsProvider,
private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider,
private ratingOffline: CoreRatingOfflineProvider) {}
/** /**
* Returns the record with the offline actions applied. * Returns the record with the offline actions applied.
* *
* @param {any} record Entry to modify. * @param {AddonModDataEntry} record Entry to modify.
* @param {any} offlineActions Offline data with the actions done. * @param {AddonModDataOfflineAction[]} offlineActions Offline data with the actions done.
* @param {any} fields Entry defined fields indexed by fieldid. * @param {any[]} fields Entry defined fields indexed by fieldid.
* @return {any} Modified entry. * @return {Promise<AddonModDataEntry>} Promise resolved when done.
*/ */
applyOfflineActions(record: any, offlineActions: any[], fields: any[]): any { applyOfflineActions(record: AddonModDataEntry, offlineActions: AddonModDataOfflineAction[], fields: any[]):
Promise<AddonModDataEntry> {
const promises = []; const promises = [];
offlineActions.forEach((action) => { offlineActions.forEach((action) => {
record.timemodified = action.timemodified;
record.hasOffline = true;
switch (action.action) { switch (action.action) {
case 'approve': case 'approve':
record.approved = true; record.approved = true;
@ -56,6 +68,8 @@ export class AddonModDataHelperProvider {
break; break;
case 'add': case 'add':
case 'edit': case 'edit':
record.groupid = action.groupid;
const offlineContents = {}; const offlineContents = {};
action.fields.forEach((offlineContent) => { action.fields.forEach((offlineContent) => {
@ -77,10 +91,12 @@ export class AddonModDataHelperProvider {
promises.push(this.getStoredFiles(record.dataid, record.id, field.id).then((offlineFiles) => { promises.push(this.getStoredFiles(record.dataid, record.id, field.id).then((offlineFiles) => {
record.contents[field.id] = this.fieldsDelegate.overrideData(field, record.contents[field.id], record.contents[field.id] = this.fieldsDelegate.overrideData(field, record.contents[field.id],
offlineContents[field.id], offlineFiles); offlineContents[field.id], offlineFiles);
record.contents[field.id].fieldid = field.id;
})); }));
} else { } else {
record.contents[field.id] = this.fieldsDelegate.overrideData(field, record.contents[field.id], record.contents[field.id] = this.fieldsDelegate.overrideData(field, record.contents[field.id],
offlineContents[field.id]); offlineContents[field.id]);
record.contents[field.id].fieldid = field.id;
} }
}); });
break; break;
@ -94,18 +110,59 @@ export class AddonModDataHelperProvider {
}); });
} }
/**
* Approve or disapprove a database entry.
*
* @param {number} dataId Database ID.
* @param {number} entryId Entry ID.
* @param {boolaen} approve True to approve, false to disapprove.
* @param {number} [courseId] Course ID. It not defined, it will be fetched.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
approveOrDisapproveEntry(dataId: number, entryId: number, approve: boolean, courseId?: number, siteId?: string): void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const modal = this.domUtils.showModalLoading('core.sending', true);
this.getActivityCourseIdIfNotSet(dataId, courseId, siteId).then((courseId) => {
// Approve/disapprove entry.
return this.dataProvider.approveEntry(dataId, entryId, approve, courseId, siteId).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errorapproving', true);
return Promise.reject(null);
});
}).then(() => {
const promises = [];
promises.push(this.dataProvider.invalidateEntryData(dataId, entryId, siteId));
promises.push(this.dataProvider.invalidateEntriesData(dataId, siteId));
return Promise.all(promises).catch(() => {
// Ignore errors.
});
}).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId}, siteId);
this.domUtils.showToast(approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', true, 3000);
}).catch(() => {
// Ignore error, it was already displayed.
}).finally(() => {
modal.dismiss();
});
}
/** /**
* Displays fields for being shown. * Displays fields for being shown.
* *
* @param {string} template Template HMTL. * @param {string} template Template HMTL.
* @param {any[]} fields Fields that defines every content in the entry. * @param {any[]} fields Fields that defines every content in the entry.
* @param {any} entry Entry. * @param {any} entry Entry.
* @param {number} offset Entry offset. * @param {number} offset Entry offset.
* @param {string} mode Mode list or show. * @param {string} mode Mode list or show.
* @param {any} actions Actions that can be performed to the record. * @param {AddonModDataOfflineAction[]} actions Actions that can be performed to the record.
* @return {string} Generated HTML. * @return {string} Generated HTML.
*/ */
displayShowFields(template: string, fields: any[], entry: any, offset: number, mode: string, actions: any): string { displayShowFields(template: string, fields: any[], entry: any, offset: number, mode: string,
actions: AddonModDataOfflineAction[]): string {
if (!template) { if (!template) {
return ''; return '';
} }
@ -135,8 +192,8 @@ export class AddonModDataHelperProvider {
} else if (action == 'approvalstatus') { } else if (action == 'approvalstatus') {
render = this.translate.instant('addon.mod_data.' + (entry.approved ? 'approved' : 'notapproved')); render = this.translate.instant('addon.mod_data.' + (entry.approved ? 'approved' : 'notapproved'));
} else { } else {
render = '<addon-mod-data-action action="' + action + '" [entry]="entries[' + entry.id + render = '<addon-mod-data-action action="' + action + '" [entry]="entries[' + entry.id + ']" mode="' + mode +
']" mode="' + mode + '" [database]="data" [offset]="' + offset + '"></addon-mod-data-action>'; '" [database]="data" [module]="module" [offset]="' + offset + '" [group]="group" ></addon-mod-data-action>';
} }
template = template.replace(replace, render); template = template.replace(replace, render);
} else { } else {
@ -147,6 +204,153 @@ export class AddonModDataHelperProvider {
return template; return template;
} }
/**
* Get online and offline entries, or search entries.
*
* @param {any} data Database object.
* @param {any[]} fields The fields that define the contents.
* @param {number} [groupId=0] Group ID.
* @param {string} [search] Search text. It will be used if advSearch is not defined.
* @param {any[]} [advSearch] Advanced search data.
* @param {string} [sort=0] Sort the records by this field id, reserved ids are:
* 0: timeadded
* -1: firstname
* -2: lastname
* -3: approved
* -4: timemodified.
* Empty for using the default database setting.
* @param {string} [order=DESC] The direction of the sorting: 'ASC' or 'DESC'.
* Empty for using the default database setting.
* @param {number} [page=0] Page of records to return.
* @param {number} [perPage=PER_PAGE] Records per page to return. Default on PER_PAGE.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<AddonModDataEntries>} Promise resolved when the database is retrieved.
*/
fetchEntries(data: any, fields: any[], groupId: number = 0, search?: string, advSearch?: any[], sort: string = '0',
order: string = 'DESC', page: number = 0, perPage: number = AddonModDataProvider.PER_PAGE, siteId?: string):
Promise<AddonModDataEntries> {
return this.sitesProvider.getSite(siteId).then((site) => {
const offlineActions = {};
const result: AddonModDataEntries = {
entries: [],
totalcount: 0,
offlineEntries: []
};
const offlinePromise = this.dataOffline.getDatabaseEntries(data.id, site.id).then((actions) => {
result.hasOfflineActions = !!actions.length;
actions.forEach((action) => {
if (typeof offlineActions[action.entryid] == 'undefined') {
offlineActions[action.entryid] = [];
}
offlineActions[action.entryid].push(action);
// We only display new entries in the first page when not searching.
if (action.action == 'add' && page == 0 && !search && !advSearch &&
(!action.groupid || !groupId || action.groupid == groupId)) {
result.offlineEntries.push({
id: action.entryid,
canmanageentry: true,
approved: !data.approval || data.manageapproved,
dataid: data.id,
groupid: action.groupid,
timecreated: -action.entryid,
timemodified: -action.entryid,
userid: site.getUserId(),
fullname: site.getInfo().fullname,
contents: {}
});
}
});
// Sort offline entries by creation time.
result.offlineEntries.sort((entry1, entry2) => entry2.timecreated - entry1.timecreated);
});
const ratingsPromise = this.ratingOffline.hasRatings('mod_data', 'entry', 'module', data.coursemodule)
.then((hasRatings) => {
result.hasOfflineRatings = hasRatings;
});
let fetchPromise: Promise<void>;
if (search || advSearch) {
fetchPromise = this.dataProvider.searchEntries(data.id, groupId, search, advSearch, sort, order, page, perPage,
site.id).then((fetchResult) => {
result.entries = fetchResult.entries;
result.totalcount = fetchResult.totalcount;
result.maxcount = fetchResult.maxcount;
});
} else {
fetchPromise = this.dataProvider.getEntries(data.id, groupId, sort, order, page, perPage, false, false, site.id)
.then((fetchResult) => {
result.entries = fetchResult.entries;
result.totalcount = fetchResult.totalcount;
});
}
return Promise.all([offlinePromise, ratingsPromise, fetchPromise]).then(() => {
// Apply offline actions to online and offline entries.
const promises = [];
result.entries.forEach((entry) => {
promises.push(this.applyOfflineActions(entry, offlineActions[entry.id] || [], fields));
});
result.offlineEntries.forEach((entry) => {
promises.push(this.applyOfflineActions(entry, offlineActions[entry.id] || [], fields));
});
return Promise.all(promises);
}).then(() => {
return result;
});
});
}
/**
* Fetch an online or offline entry.
*
* @param {any} data Database.
* @param {any[]} fields List of database fields.
* @param {number} entryId Entry ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<{entry: AddonModDataEntry, ratinginfo?: CoreRatingInfo}>} Promise resolved with the entry.
*/
fetchEntry(data: any, fields: any[], entryId: number, siteId?: string):
Promise<{entry: AddonModDataEntry, ratinginfo?: CoreRatingInfo}> {
return this.sitesProvider.getSite(siteId).then((site) => {
return this.dataOffline.getEntryActions(data.id, entryId, site.id).then((offlineActions) => {
let promise: Promise<{entry: AddonModDataEntry, ratinginfo?: CoreRatingInfo}>;
if (entryId > 0) {
// Online entry.
promise = this.dataProvider.getEntry(data.id, entryId, false, site.id);
} else {
// Offline entry or new entry.
promise = Promise.resolve({
entry: {
id: entryId,
userid: site.getUserId(),
groupid: 0,
dataid: data.id,
timecreated: -entryId,
timemodified: -entryId,
approved: !data.approval || data.manageapproved,
canmanageentry: true,
fullname: site.getInfo().fullname,
contents: [],
}
});
}
return promise.then((response) => {
return this.applyOfflineActions(response.entry, offlineActions, fields).then(() => {
return response;
});
});
});
});
}
/** /**
* Returns an object with all the actions that the user can do over the record. * Returns an object with all the actions that the user can do over the record.
* *
@ -179,6 +383,24 @@ export class AddonModDataHelperProvider {
}; };
} }
/**
* Convenience function to get the course id of the database.
*
* @param {number} dataId Database id.
* @param {number} [courseId] Course id, if known.
* @param {string} [siteId] Site id, if not set, current site will be used.
* @return {Promise<number>} Resolved with course Id when done.
*/
protected getActivityCourseIdIfNotSet(dataId: number, courseId?: number, siteId?: string): Promise<number> {
if (courseId) {
return Promise.resolve(courseId);
}
return this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => {
return module.course;
});
}
/** /**
* Returns the default template of a certain type. * Returns the default template of a certain type.
* *
@ -256,17 +478,17 @@ export class AddonModDataHelperProvider {
* Retrieve the entered data in the edit form. * Retrieve the entered data in the edit form.
* We don't use ng-model because it doesn't detect changes done by JavaScript. * We don't use ng-model because it doesn't detect changes done by JavaScript.
* *
* @param {any} inputData Array with the entered form values. * @param {any} inputData Array with the entered form values.
* @param {Array} fields Fields that defines every content in the entry. * @param {Array} fields Fields that defines every content in the entry.
* @param {number} [dataId] Database Id. If set, files will be uploaded and itemId set. * @param {number} [dataId] Database Id. If set, files will be uploaded and itemId set.
* @param {number} entryId Entry Id. * @param {number} entryId Entry Id.
* @param {any} entryContents Original entry contents indexed by field id. * @param {AddonModDataEntryFields} entryContents Original entry contents.
* @param {boolean} offline True to prepare the data for an offline uploading, false otherwise. * @param {boolean} offline True to prepare the data for an offline uploading, false otherwise.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} That contains object with the answers. * @return {Promise<any>} That contains object with the answers.
*/ */
getEditDataFromForm(inputData: any, fields: any, dataId: number, entryId: number, entryContents: any, offline: boolean = false, getEditDataFromForm(inputData: any, fields: any, dataId: number, entryId: number, entryContents: AddonModDataEntryFields,
siteId?: string): Promise<any> { offline: boolean = false, siteId?: string): Promise<any> {
if (!inputData) { if (!inputData) {
return Promise.resolve({}); return Promise.resolve({});
} }
@ -322,13 +544,13 @@ export class AddonModDataHelperProvider {
/** /**
* Retrieve the temp files to be updated. * Retrieve the temp files to be updated.
* *
* @param {any} inputData Array with the entered form values. * @param {any} inputData Array with the entered form values.
* @param {Array} fields Fields that defines every content in the entry. * @param {any[]} fields Fields that defines every content in the entry.
* @param {number} [dataId] Database Id. If set, fils will be uploaded and itemId set. * @param {number} [dataId] Database Id. If set, fils will be uploaded and itemId set.
* @param {any} entryContents Original entry contents indexed by field id. * @param {AddonModDataEntryFields} entryContents Original entry contents indexed by field id.
* @return {Promise<any>} That contains object with the files. * @return {Promise<any>} That contains object with the files.
*/ */
getEditTmpFiles(inputData: any, fields: any, dataId: number, entryContents: any): Promise<any> { getEditTmpFiles(inputData: any, fields: any[], dataId: number, entryContents: AddonModDataEntryFields): Promise<any> {
if (!inputData) { if (!inputData) {
return Promise.resolve([]); return Promise.resolve([]);
} }
@ -343,45 +565,6 @@ export class AddonModDataHelperProvider {
}); });
} }
/**
* Get an online or offline entry.
*
* @param {any} data Database.
* @param {number} entryId Entry ID.
* @param {any} [offlineActions] Offline data with the actions done. Required for offline entries.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with the entry.
*/
getEntry(data: any, entryId: number, offlineActions?: any, siteId?: string): Promise<any> {
if (entryId > 0) {
// It's an online entry, get it from WS.
return this.dataProvider.getEntry(data.id, entryId, false, siteId);
}
// It's an offline entry, search it in the offline actions.
return this.sitesProvider.getSite(siteId).then((site) => {
const offlineEntry = offlineActions.find((offlineAction) => offlineAction.action == 'add');
if (offlineEntry) {
const siteInfo = site.getInfo();
return {entry: {
id: offlineEntry.entryid,
canmanageentry: true,
approved: !data.approval || data.manageapproved,
dataid: offlineEntry.dataid,
groupid: offlineEntry.groupid,
timecreated: -offlineEntry.entryid,
timemodified: -offlineEntry.entryid,
userid: siteInfo.userid,
fullname: siteInfo.fullname,
contents: {}
}
};
}
});
}
/** /**
* Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles. * Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles.
* *
@ -403,13 +586,13 @@ export class AddonModDataHelperProvider {
/** /**
* Check if data has been changed by the user. * Check if data has been changed by the user.
* *
* @param {any} inputData Array with the entered form values. * @param {any} inputData Object with the entered form values.
* @param {any} fields Fields that defines every content in the entry. * @param {any[]} fields Fields that defines every content in the entry.
* @param {number} [dataId] Database Id. If set, fils will be uploaded and itemId set. * @param {number} [dataId] Database Id. If set, fils will be uploaded and itemId set.
* @param {any} entryContents Original entry contents indexed by field id. * @param {AddonModDataEntryFields} entryContents Original entry contents indexed by field id.
* @return {Promise<boolean>} True if changed, false if not. * @return {Promise<boolean>} True if changed, false if not.
*/ */
hasEditDataChanged(inputData: any, fields: any, dataId: number, entryContents: any): Promise<boolean> { hasEditDataChanged(inputData: any, fields: any[], dataId: number, entryContents: AddonModDataEntryFields): Promise<boolean> {
const promises = fields.map((field) => { const promises = fields.map((field) => {
return this.fieldsDelegate.hasFieldDataChanged(field, inputData, entryContents[field.id]); return this.fieldsDelegate.hasFieldDataChanged(field, inputData, entryContents[field.id]);
}); });
@ -424,6 +607,45 @@ export class AddonModDataHelperProvider {
}); });
} }
/**
* Displays a confirmation modal for deleting an entry.
*
* @param {number} dataId Database ID.
* @param {number} entryId Entry ID.
* @param {number} [courseId] Course ID. It not defined, it will be fetched.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
this.domUtils.showConfirm(this.translate.instant('addon.mod_data.confirmdeleterecord')).then(() => {
const modal = this.domUtils.showModalLoading();
return this.getActivityCourseIdIfNotSet(dataId, courseId, siteId).then((courseId) => {
return this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId);
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true);
return Promise.reject(null);
}).then(() => {
return this.utils.allPromises([
this.dataProvider.invalidateEntryData(dataId, entryId, siteId),
this.dataProvider.invalidateEntriesData(dataId, siteId)
]).catch(() => {
// Ignore errors.
});
}).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId, entryId, deleted: true}, siteId);
this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000);
}).finally(() => {
modal.dismiss();
});
}).catch(() => {
// Ignore error, it was already displayed.
});
}
/** /**
* Given a list of files (either online files or local files), store the local files in a local folder * Given a list of files (either online files or local files), store the local files in a local folder
* to be submitted later. * to be submitted later.

View File

@ -16,9 +16,24 @@ import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFileProvider } from '@providers/file'; import { CoreFileProvider } from '@providers/file';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { SQLiteDB } from '@classes/sqlitedb'; import { SQLiteDB } from '@classes/sqlitedb';
import { AddonModDataSubfieldData } from './data';
/**
* Entry action stored offline.
*/
export interface AddonModDataOfflineAction {
dataid: number;
courseid: number;
groupid: number;
action: string;
entryid: number; // Negative for offline entries.
fields: AddonModDataSubfieldData[];
timemodified: number;
}
/** /**
* Service to handle Offline data. * Service to handle Offline data.
@ -87,7 +102,8 @@ export class AddonModDataOfflineProvider {
}; };
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
private fileProvider: CoreFileProvider, private fileUploaderProvider: CoreFileUploaderProvider) { private fileProvider: CoreFileProvider, private fileUploaderProvider: CoreFileUploaderProvider,
private utils: CoreUtilsProvider) {
this.logger = logger.getInstance('AddonModDataOfflineProvider'); this.logger = logger.getInstance('AddonModDataOfflineProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema); this.sitesProvider.registerSiteSchema(this.siteSchema);
} }
@ -175,10 +191,10 @@ export class AddonModDataOfflineProvider {
/** /**
* Get all the stored entry data from all the databases. * Get all the stored entry data from all the databases.
* *
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with entries. * @return {Promise<AddonModDataOfflineAction[]>} Promise resolved with entries.
*/ */
getAllEntries(siteId?: string): Promise<any> { getAllEntries(siteId?: string): Promise<AddonModDataOfflineAction[]> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getAllRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE); return site.getDb().getAllRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE);
}).then((entries) => { }).then((entries) => {
@ -187,15 +203,15 @@ export class AddonModDataOfflineProvider {
} }
/** /**
* Get all the stored entry data from a certain database. * Get all the stored entry actions from a certain database, sorted by modification time.
* *
* @param {number} dataId Database ID. * @param {number} dataId Database ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with entries. * @return {Promise<AddonModDataOfflineAction[]>} Promise resolved with entries.
*/ */
getDatabaseEntries(dataId: number, siteId?: string): Promise<any> { getDatabaseEntries(dataId: number, siteId?: string): Promise<AddonModDataOfflineAction[]> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId}); return site.getDb().getRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId}, 'timemodified');
}).then((entries) => { }).then((entries) => {
return entries.map(this.parseRecord.bind(this)); return entries.map(this.parseRecord.bind(this));
}); });
@ -208,9 +224,9 @@ export class AddonModDataOfflineProvider {
* @param {number} entryId Database entry Id. * @param {number} entryId Database entry Id.
* @param {string} action Action to be done * @param {string} action Action to be done
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with entry. * @return {Promise<AddonModDataOfflineAction>} Promise resolved with entry.
*/ */
getEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise<any> { getEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise<AddonModDataOfflineAction> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecord(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, return site.getDb().getRecord(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId,
action: action}); action: action});
@ -225,9 +241,9 @@ export class AddonModDataOfflineProvider {
* @param {number} dataId Database ID. * @param {number} dataId Database ID.
* @param {number} entryId Database entry Id. * @param {number} entryId Database entry Id.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with entry actions. * @return {Promise<AddonModDataOfflineAction[]>} Promise resolved with entry actions.
*/ */
getEntryActions(dataId: number, entryId: number, siteId?: string): Promise<any> { getEntryActions(dataId: number, entryId: number, siteId?: string): Promise<AddonModDataOfflineAction[]> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId}); return site.getDb().getRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId});
}).then((entries) => { }).then((entries) => {
@ -243,11 +259,10 @@ export class AddonModDataOfflineProvider {
* @return {Promise<any>} Promise resolved with boolean: true if has offline answers, false otherwise. * @return {Promise<any>} Promise resolved with boolean: true if has offline answers, false otherwise.
*/ */
hasOfflineData(dataId: number, siteId?: string): Promise<any> { hasOfflineData(dataId: number, siteId?: string): Promise<any> {
return this.getDatabaseEntries(dataId, siteId).then((entries) => { return this.sitesProvider.getSite(siteId).then((site) => {
return !!entries.length; return this.utils.promiseWorks(
}).catch(() => { site.getDb().recordExists(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId})
// No offline data found, return false. );
return false;
}); });
} }
@ -286,10 +301,10 @@ export class AddonModDataOfflineProvider {
/** /**
* Parse "fields" of an offline record. * Parse "fields" of an offline record.
* *
* @param {any} record Record object * @param {any} record Record object
* @return {any} Record object with columns parsed. * @return {AddonModDataOfflineAction} Record object with columns parsed.
*/ */
protected parseRecord(record: any): any { protected parseRecord(record: any): AddonModDataOfflineAction {
record.fields = this.textUtils.parseJSON(record.fields); record.fields = this.textUtils.parseJSON(record.fields);
return record; return record;
@ -308,8 +323,8 @@ export class AddonModDataOfflineProvider {
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure. * @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/ */
saveEntry(dataId: number, entryId: number, action: string, courseId: number, groupId?: number, fields?: any[], saveEntry(dataId: number, entryId: number, action: string, courseId: number, groupId?: number,
timemodified?: number, siteId?: string): Promise<any> { fields?: AddonModDataSubfieldData[], timemodified?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
timemodified = timemodified || new Date().getTime(); timemodified = timemodified || new Date().getTime();

View File

@ -25,7 +25,7 @@ import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
import { CoreRatingProvider } from '@core/rating/providers/rating'; import { CoreRatingProvider } from '@core/rating/providers/rating';
import { AddonModDataProvider } from './data'; import { AddonModDataProvider, AddonModDataEntry } from './data';
import { AddonModDataSyncProvider } from './sync'; import { AddonModDataSyncProvider } from './sync';
import { AddonModDataHelperProvider } from './helper'; import { AddonModDataHelperProvider } from './helper';
@ -57,10 +57,10 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. * @param {string} [siteId] Site ID.
* @return {Promise<any>} All unique entries. * @return {Promise<AddonModDataEntry[]>} All unique entries.
*/ */
protected getAllUniqueEntries(dataId: number, groups: any[], forceCache: boolean = false, ignoreCache: boolean = false, protected getAllUniqueEntries(dataId: number, groups: any[], forceCache: boolean = false, ignoreCache: boolean = false,
siteId?: string): Promise<any> { siteId?: string): Promise<AddonModDataEntry[]> {
const promises = groups.map((group) => { const promises = groups.map((group) => {
return this.dataProvider.fetchAllEntries(dataId, group.id, undefined, undefined, undefined, forceCache, ignoreCache, return this.dataProvider.fetchAllEntries(dataId, group.id, undefined, undefined, undefined, forceCache, ignoreCache,
siteId); siteId);
@ -139,14 +139,14 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl
/** /**
* Returns the file contained in the entries. * Returns the file contained in the entries.
* *
* @param {any[]} entries List of entries to get files from. * @param {AddonModDataEntry[]} entries List of entries to get files from.
* @return {any[]} List of files. * @return {any[]} List of files.
*/ */
protected getEntriesFiles(entries: any[]): any[] { protected getEntriesFiles(entries: AddonModDataEntry[]): any[] {
let files = []; let files = [];
entries.forEach((entry) => { entries.forEach((entry) => {
entry.contents.forEach((content) => { this.utils.objectToArray(entry.contents).forEach((content) => {
files = files.concat(content.files); files = files.concat(content.files);
}); });
}); });

View File

@ -20,7 +20,7 @@ import { CoreAppProvider } from '@providers/app';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { AddonModDataOfflineProvider } from './offline'; import { AddonModDataOfflineProvider, AddonModDataOfflineAction } from './offline';
import { AddonModDataProvider } from './data'; import { AddonModDataProvider } from './data';
import { AddonModDataHelperProvider } from './helper'; import { AddonModDataHelperProvider } from './helper';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
@ -174,7 +174,7 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
// No offline data found, return empty object. // No offline data found, return empty object.
return []; return [];
}); });
}).then((offlineActions) => { }).then((offlineActions: AddonModDataOfflineAction[]) => {
if (!offlineActions.length) { if (!offlineActions.length) {
// Nothing to sync. // Nothing to sync.
return; return;
@ -226,35 +226,41 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
/** /**
* Synchronize an entry. * Synchronize an entry.
* *
* @param {any} data Database. * @param {any} data Database.
* @param {any} entryActions Entry actions. * @param {AddonModDataOfflineAction[]} entryActions Entry actions.
* @param {any} result Object with the result of the sync. * @param {any} result Object with the result of the sync.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if success, rejected otherwise. * @return {Promise<any>} Promise resolved if success, rejected otherwise.
*/ */
protected syncEntry(data: any, entryActions: any[], result: any, siteId?: string): Promise<any> { protected syncEntry(data: any, entryActions: AddonModDataOfflineAction[], result: any, siteId?: string): Promise<any> {
let discardError, let discardError,
timePromise, timePromise,
entryId = 0, entryId = entryActions[0].entryid,
offlineId, offlineId,
deleted = false; deleted = false;
const promises = []; const editAction = entryActions.find((action) => action.action == 'add' || action.action == 'edit');
const approveAction = entryActions.find((action) => action.action == 'approve' || action.action == 'disapprove');
// Sort entries by timemodified. const deleteAction = entryActions.find((action) => action.action == 'delete');
entryActions = entryActions.sort((a: any, b: any) => a.timemodified - b.timemodified);
entryId = entryActions[0].entryid;
if (entryId > 0) { if (entryId > 0) {
timePromise = this.dataProvider.getEntry(data.id, entryId, false, siteId).then((entry) => { timePromise = this.dataProvider.getEntry(data.id, entryId, true, siteId).then((entry) => {
return entry.entry.timemodified; return entry.entry.timemodified;
}).catch(() => { }).catch((error) => {
return -1; if (error && this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means the entry has been deleted.
return Promise.resolve(-1);
}
return Promise.reject(error);
}); });
} else { } else if (editAction) {
// New entry.
offlineId = entryId; offlineId = entryId;
timePromise = Promise.resolve(0); timePromise = Promise.resolve(0);
} else {
// New entry but the add action is missing, discard.
timePromise = Promise.resolve(-1);
} }
return timePromise.then((timemodified) => { return timePromise.then((timemodified) => {
@ -266,58 +272,11 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
return this.dataOffline.deleteAllEntryActions(data.id, entryId, siteId); return this.dataOffline.deleteAllEntryActions(data.id, entryId, siteId);
} }
entryActions.forEach((action) => { if (deleteAction) {
let actionPromise; return this.dataProvider.deleteEntryOnline(entryId, siteId).then(() => {
const proms = []; deleted = true;
}).catch((error) => {
entryId = action.entryid > 0 ? action.entryid : entryId; if (error && this.utils.isWebServiceError(error)) {
if (action.fields) {
action.fields.forEach((field) => {
// Upload Files if asked.
const value = this.textUtils.parseJSON(field.value);
if (value.online || value.offline) {
let files = value.online || [];
const fileProm = value.offline ? this.dataHelper.getStoredFiles(action.dataid, entryId, field.fieldid) :
Promise.resolve([]);
proms.push(fileProm.then((offlineFiles) => {
files = files.concat(offlineFiles);
return this.dataHelper.uploadOrStoreFiles(action.dataid, 0, entryId, field.fieldid, files, false,
siteId).then((filesResult) => {
field.value = JSON.stringify(filesResult);
});
}));
}
});
}
actionPromise = Promise.all(proms).then(() => {
// Perform the action.
switch (action.action) {
case 'add':
return this.dataProvider.addEntryOnline(action.dataid, action.fields, data.groupid, siteId)
.then((result) => {
entryId = result.newentryid;
});
case 'edit':
return this.dataProvider.editEntryOnline(entryId, action.fields, siteId);
case 'approve':
return this.dataProvider.approveEntryOnline(entryId, true, siteId);
case 'disapprove':
return this.dataProvider.approveEntryOnline(entryId, false, siteId);
case 'delete':
return this.dataProvider.deleteEntryOnline(entryId, siteId).then(() => {
deleted = true;
});
default:
break;
}
});
promises.push(actionPromise.catch((error) => {
if (error && error.wserror) {
// The WebService has thrown an error, this means it cannot be performed. Discard. // The WebService has thrown an error, this means it cannot be performed. Discard.
discardError = this.textUtils.getErrorMessageFromError(error); discardError = this.textUtils.getErrorMessageFromError(error);
} else { } else {
@ -328,11 +287,79 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
// Delete the offline data. // Delete the offline data.
result.updated = true; result.updated = true;
return this.dataOffline.deleteEntry(action.dataid, action.entryid, action.action, siteId); return this.dataOffline.deleteAllEntryActions(deleteAction.dataid, deleteAction.entryid, siteId);
})); });
}); }
let editPromise;
if (editAction) {
editPromise = Promise.all(editAction.fields.map((field) => {
// Upload Files if asked.
const value = this.textUtils.parseJSON(field.value);
if (value.online || value.offline) {
let files = value.online || [];
const fileProm = value.offline ?
this.dataHelper.getStoredFiles(editAction.dataid, entryId, field.fieldid) :
Promise.resolve([]);
return fileProm.then((offlineFiles) => {
files = files.concat(offlineFiles);
return this.dataHelper.uploadOrStoreFiles(editAction.dataid, 0, entryId, field.fieldid, files,
false, siteId).then((filesResult) => {
field.value = JSON.stringify(filesResult);
});
});
}
})).then(() => {
if (editAction.action == 'add') {
return this.dataProvider.addEntryOnline(editAction.dataid, editAction.fields, editAction.groupid, siteId)
.then((result) => {
entryId = result.newentryid;
});
} else {
return this.dataProvider.editEntryOnline(entryId, editAction.fields, siteId);
}
}).catch((error) => {
if (error && this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means it cannot be performed. Discard.
discardError = this.textUtils.getErrorMessageFromError(error);
} else {
// Couldn't connect to server, reject.
return Promise.reject(error);
}
}).then(() => {
// Delete the offline data.
result.updated = true;
return this.dataOffline.deleteEntry(editAction.dataid, editAction.entryid, editAction.action, siteId);
});
} else {
editPromise = Promise.resolve();
}
if (approveAction) {
editPromise = editPromise.then(() => {
return this.dataProvider.approveEntryOnline(entryId, approveAction.action == 'approve', siteId);
}).catch((error) => {
if (error && this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means it cannot be performed. Discard.
discardError = this.textUtils.getErrorMessageFromError(error);
} else {
// Couldn't connect to server, reject.
return Promise.reject(error);
}
}).then(() => {
// Delete the offline data.
result.updated = true;
return this.dataOffline.deleteEntry(approveAction.dataid, approveAction.entryid, approveAction.action, siteId);
});
}
return editPromise;
return Promise.all(promises);
}).then(() => { }).then(() => {
if (discardError) { if (discardError) {
// Submission was discarded, add a warning. // Submission was discarded, add a warning.