+
+
+
+ {{ 'addon.mod_data.resetsettings' | translate}}
+
+
+
+
diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts
new file mode 100644
index 000000000..409bf230f
--- /dev/null
+++ b/src/addon/mod/data/components/index/index.ts
@@ -0,0 +1,494 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, Optional, Injector } from '@angular/core';
+import { Content, ModalController, NavController } from 'ionic-angular';
+import { CoreTimeUtilsProvider } from '@providers/utils/time';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
+import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
+import { CoreCommentsProvider } from '@core/comments/providers/comments';
+import { AddonModDataProvider } from '../../providers/data';
+import { AddonModDataHelperProvider } from '../../providers/helper';
+import { AddonModDataOfflineProvider } from '../../providers/offline';
+import { AddonModDataSyncProvider } from '../../providers/sync';
+import { AddonModDataComponentsModule } from '../components.module';
+import * as moment from 'moment';
+
+/**
+ * Component that displays a data index page.
+ */
+@Component({
+ selector: 'addon-mod-data-index',
+ templateUrl: 'index.html',
+})
+export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComponent {
+
+ component = AddonModDataProvider.COMPONENT;
+ moduleName = 'data';
+
+ access: any = {};
+ data: any = {};
+ fields: any;
+ selectedGroup: number;
+ timeAvailableFrom: number | boolean;
+ timeAvailableFromReadable: string | boolean;
+ timeAvailableTo: number | boolean;
+ timeAvailableToReadable: string | boolean;
+ isEmpty = false;
+ groupInfo: CoreGroupInfo;
+ entries = {};
+ firstEntry = false;
+ canAdd = false;
+ canSearch = false;
+ search = {
+ sortBy: '0',
+ sortDirection: 'DESC',
+ page: 0,
+ text: '',
+ searching: false,
+ searchingAdvanced: false,
+ advanced: []
+ };
+ hasNextPage = false;
+ offlineActions: any;
+ offlineEntries: any;
+ entriesRendered = '';
+ cssTemplate = '';
+ extraImports = [AddonModDataComponentsModule];
+ jsData;
+
+ protected syncEventName = AddonModDataSyncProvider.AUTO_SYNCED;
+ protected entryChangedObserver: any;
+ protected hasComments = false;
+ protected fieldsArray: any;
+
+ constructor(injector: Injector, private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider,
+ private dataOffline: AddonModDataOfflineProvider, @Optional() content: Content,
+ private dataSync: AddonModDataSyncProvider, private timeUtils: CoreTimeUtilsProvider,
+ private groupsProvider: CoreGroupsProvider, private commentsProvider: CoreCommentsProvider,
+ private modalCtrl: ModalController, private utils: CoreUtilsProvider, protected navCtrl: NavController) {
+ super(injector, content);
+
+ // Refresh entries on change.
+ this.entryChangedObserver = this.eventsProvider.on(AddonModDataProvider.ENTRY_CHANGED, (eventData) => {
+ if (this.data.id == eventData.dataId) {
+ this.loaded = false;
+
+ return this.loadContent(true);
+ }
+ }, this.siteId);
+ }
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ super.ngOnInit();
+
+ this.selectedGroup = this.group || 0;
+
+ this.loadContent(false, true).then(() => {
+ if (!this.data) {
+ return;
+ }
+
+ this.dataProvider.logView(this.data.id).then(() => {
+ this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
+ });
+ });
+
+ // Setup search modal.
+ }
+
+ /**
+ * Perform the invalidate content function.
+ *
+ * @return {Promise} Resolved when done.
+ */
+ protected invalidateContent(): Promise {
+ const promises = [];
+
+ promises.push(this.dataProvider.invalidateDatabaseData(this.courseId));
+ if (this.data) {
+ promises.push(this.dataProvider.invalidateDatabaseAccessInformationData(this.data.id));
+ promises.push(this.groupsProvider.invalidateActivityGroupInfo(this.data.coursemodule));
+ promises.push(this.dataProvider.invalidateEntriesData(this.data.id));
+ if (this.hasComments) {
+ promises.push(this.commentsProvider.invalidateCommentsByInstance('module', this.data.coursemodule));
+ }
+ }
+
+ return Promise.all(promises);
+ }
+
+ /**
+ * Compares sync event data with current data to check if refresh content is needed.
+ *
+ * @param {any} syncEventData Data receiven on sync observer.
+ * @return {boolean} True if refresh is needed, false otherwise.
+ */
+ protected isRefreshSyncNeeded(syncEventData: any): boolean {
+ if (this.data && syncEventData.dataId == this.data.id && typeof syncEventData.entryId == 'undefined') {
+ this.loaded = false;
+ // Refresh the data.
+ this.content.scrollToTop();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Download data contents.
+ *
+ * @param {boolean} [refresh=false] If it's refreshing content.
+ * @param {boolean} [sync=false] If the refresh is needs syncing.
+ * @param {boolean} [showErrors=false] If show errors to the user of hide them.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise {
+ let canAdd = false,
+ canSearch = false;
+
+ return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => {
+ this.data = data;
+
+ this.description = data.intro || data.description;
+ this.dataRetrieved.emit(data);
+
+ if (sync) {
+ // Try to synchronize the data.
+ return this.syncActivity(showErrors).catch(() => {
+ // Ignore errors.
+ });
+ }
+ }).then(() => {
+ return this.dataProvider.getDatabaseAccessInformation(this.data.id);
+ }).then((accessData) => {
+ this.access = accessData;
+
+ if (!accessData.timeavailable) {
+ const time = this.timeUtils.timestamp();
+
+ this.timeAvailableFrom = this.data.timeavailablefrom && time < this.data.timeavailablefrom ?
+ parseInt(this.data.timeavailablefrom, 10) * 1000 : false;
+ this.timeAvailableFromReadable = this.timeAvailableFrom ?
+ moment(this.timeAvailableFrom).format('LLL') : false;
+ this.timeAvailableTo = this.data.timeavailableto && time > this.data.timeavailableto ?
+ parseInt(this.data.timeavailableto, 10) * 1000 : false;
+ this.timeAvailableToReadable = this.timeAvailableTo ? moment(this.timeAvailableTo).format('LLL') : false;
+
+ this.isEmpty = true;
+ this.groupInfo = null;
+
+ return;
+ }
+
+ canSearch = true;
+ canAdd = accessData.canaddentry;
+
+ return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule, accessData.canmanageentries)
+ .then((groupInfo) => {
+ this.groupInfo = groupInfo;
+
+ // Check selected group is accessible.
+ if (groupInfo && groupInfo.groups && groupInfo.groups.length > 0) {
+ if (!groupInfo.groups.some((group) => this.selectedGroup == group.id)) {
+ this.selectedGroup = groupInfo.groups[0].id;
+ }
+ }
+
+ return this.fetchOfflineEntries();
+ });
+ }).then(() => {
+ return this.dataProvider.getFields(this.data.id).then((fields) => {
+ if (fields.length == 0) {
+ canSearch = false;
+ canAdd = false;
+ }
+ this.search.advanced = [];
+
+ this.fields = this.utils.arrayToObject(fields, 'id');
+ this.fieldsArray = this.utils.objectToArray(this.fields);
+
+ return this.fetchEntriesData();
+ });
+ }).then(() => {
+ // All data obtained, now fill the context menu.
+ this.fillContextMenu(refresh);
+ }).finally(() => {
+ this.canAdd = canAdd;
+ this.canSearch = canSearch;
+ });
+ }
+
+ /**
+ * Fetch current database entries.
+ *
+ * @return {Promise} Resolved then done.
+ */
+ protected fetchEntriesData(): Promise {
+ this.hasComments = false;
+
+ return this.dataProvider.getDatabaseAccessInformation(this.data.id, this.selectedGroup).then((accessData) => {
+ // Update values for current group.
+ this.access.canaddentry = accessData.canaddentry;
+
+ if (this.search.searching) {
+ const text = this.search.searchingAdvanced ? undefined : this.search.text,
+ advanced = this.search.searchingAdvanced ? this.search.advanced : undefined;
+
+ return this.dataProvider.searchEntries(this.data.id, this.selectedGroup, text, advanced, 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) => {
+ const numEntries = (entries && entries.entries && entries.entries.length) || 0;
+ this.isEmpty = !numEntries && !Object.keys(this.offlineActions).length && !Object.keys(this.offlineEntries).length;
+ this.hasNextPage = numEntries >= AddonModDataProvider.PER_PAGE && ((this.search.page + 1) *
+ AddonModDataProvider.PER_PAGE) < entries.totalcount;
+ this.entriesRendered = '';
+
+ if (!this.isEmpty) {
+ this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.addon-data-entries-' + this.data.id);
+
+ const siteInfo = this.sitesProvider.getCurrentSite().getInfo(),
+ promises = [];
+
+ this.utils.objectToArray(this.offlineEntries).forEach((offlineActions) => {
+ const offlineEntry = offlineActions.find((offlineEntry) => offlineEntry.action == 'add');
+
+ if (offlineEntry) {
+ const entry = {
+ id: offlineEntry.entryid,
+ 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) {
+ promises.push(this.dataHelper.applyOfflineActions(entry, offlineActions, this.fieldsArray));
+ } else {
+ promises.push(Promise.resolve(entry));
+ }
+ }
+ });
+
+ entries.entries.forEach((entry) => {
+ // Index contents by fieldid.
+ entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
+
+ if (typeof this.offlineActions[entry.id] != 'undefined') {
+ promises.push(this.dataHelper.applyOfflineActions(entry, this.offlineActions[entry.id], this.fieldsArray));
+ } else {
+ promises.push(Promise.resolve(entry));
+ }
+ });
+
+ return Promise.all(promises).then((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;
+ }
+
+ entries.forEach((entry) => {
+ this.entries[entry.id] = entry;
+
+ const actions = this.dataHelper.getActions(this.data, this.access, entry);
+
+ entriesHTML += this.dataHelper.displayShowFields(this.data.listtemplate, this.fieldsArray, entry, 'list',
+ actions);
+ });
+ entriesHTML += this.data.listtemplatefooter || '';
+
+ this.entriesRendered = entriesHTML;
+
+ // Pass the input data to the component.
+ this.jsData = {
+ fields: this.fields,
+ entries: this.entries,
+ data: this.data
+ };
+ });
+ } else if (!this.search.searching) {
+ // Empty and no searching.
+ this.canSearch = false;
+ }
+ this.firstEntry = false;
+ });
+ }
+
+ /**
+ * Display the chat users modal.
+ */
+ showSearch(): void {
+ const modal = this.modalCtrl.create('AddonModDataSearchPage', {
+ search: this.search,
+ fields: this.fields,
+ data: this.data});
+ modal.onDidDismiss((data) => {
+ // Add data to search object.
+ if (data) {
+ this.search = data;
+ this.searchEntries(0);
+ }
+ });
+ modal.present();
+ }
+
+ /**
+ * Performs the search and closes the modal.
+ *
+ * @param {number} page Page number.
+ * @return {Promise} Resolved when done.
+ */
+ searchEntries(page: number): Promise {
+ this.loaded = false;
+ this.search.page = page;
+
+ return this.fetchEntriesData().catch((message) => {
+ this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
+ }).finally(() => {
+ this.loaded = true;
+ });
+ }
+
+ /**
+ * Reset all search filters and closes the modal.
+ */
+ searchReset(): void {
+ this.search.sortBy = '0';
+ this.search.sortDirection = 'DESC';
+ this.search.text = '';
+ this.search.advanced = [];
+ this.search.searchingAdvanced = false;
+ this.search.searching = false;
+ this.searchEntries(0);
+ }
+
+ /**
+ * Set group to see the database.
+ *
+ * @param {number} groupId Group ID.
+ * @return {Promise} Resolved when new group is selected or rejected if not.
+ */
+ setGroup(groupId: number): Promise {
+ this.selectedGroup = groupId;
+
+ return this.fetchEntriesData().catch((message) => {
+ this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
+
+ return Promise.reject(null);
+ });
+ }
+
+ /**
+ * Opens add entries form.
+ */
+ gotoAddEntries(): void {
+ const params = {
+ module: this.module,
+ courseId: this.courseId,
+ group: this.selectedGroup
+ };
+
+ this.navCtrl.push('AddonModDataEditPage', params);
+ }
+
+ /**
+ * Goto the selected entry.
+ *
+ * @param {number} entryId Entry ID.
+ */
+ gotoEntry(entryId: number): void {
+ const params = {
+ module: this.module,
+ courseId: this.courseId,
+ entryId: entryId,
+ group: this.selectedGroup
+ };
+
+ this.navCtrl.push('AddonModDataEntryPage', params);
+ }
+
+ /**
+ * Fetch offline entries.
+ *
+ * @return {Promise} Resolved then done.
+ */
+ protected fetchOfflineEntries(): Promise {
+ // 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);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Performs the sync of the activity.
+ *
+ * @return {Promise} Promise resolved when done.
+ */
+ protected sync(): Promise {
+ return this.dataSync.syncDatabase(this.data.id);
+ }
+
+ /**
+ * Checks if sync has succeed from result sync data.
+ *
+ * @param {any} result Data returned on the sync function.
+ * @return {boolean} If suceed or not.
+ */
+ protected hasSyncSucceed(result: any): boolean {
+ return result.updated;
+ }
+
+ /**
+ * Component being destroyed.
+ */
+ ngOnDestroy(): void {
+ super.ngOnDestroy();
+ this.entryChangedObserver && this.entryChangedObserver.off();
+ }
+}
diff --git a/src/addon/mod/data/data.module.ts b/src/addon/mod/data/data.module.ts
new file mode 100644
index 000000000..6231d8e79
--- /dev/null
+++ b/src/addon/mod/data/data.module.ts
@@ -0,0 +1,77 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { CoreCronDelegate } from '@providers/cron';
+import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
+import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
+import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
+import { AddonModDataComponentsModule } from './components/components.module';
+import { AddonModDataModuleHandler } from './providers/module-handler';
+import { AddonModDataProvider } from './providers/data';
+import { AddonModDataLinkHandler } from './providers/link-handler';
+import { AddonModDataApproveLinkHandler } from './providers/approve-link-handler';
+import { AddonModDataDeleteLinkHandler } from './providers/delete-link-handler';
+import { AddonModDataShowLinkHandler } from './providers/show-link-handler';
+import { AddonModDataEditLinkHandler } from './providers/edit-link-handler';
+import { AddonModDataHelperProvider } from './providers/helper';
+import { AddonModDataPrefetchHandler } from './providers/prefetch-handler';
+import { AddonModDataSyncProvider } from './providers/sync';
+import { AddonModDataSyncCronHandler } from './providers/sync-cron-handler';
+import { AddonModDataOfflineProvider } from './providers/offline';
+import { AddonModDataFieldsDelegate } from './providers/fields-delegate';
+import { AddonModDataDefaultFieldHandler } from './providers/default-field-handler';
+import { AddonModDataFieldModule } from './fields/field.module';
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ AddonModDataComponentsModule,
+ AddonModDataFieldModule
+ ],
+ providers: [
+ AddonModDataProvider,
+ AddonModDataModuleHandler,
+ AddonModDataPrefetchHandler,
+ AddonModDataHelperProvider,
+ AddonModDataLinkHandler,
+ AddonModDataApproveLinkHandler,
+ AddonModDataDeleteLinkHandler,
+ AddonModDataShowLinkHandler,
+ AddonModDataEditLinkHandler,
+ AddonModDataSyncCronHandler,
+ AddonModDataSyncProvider,
+ AddonModDataOfflineProvider,
+ AddonModDataFieldsDelegate,
+ AddonModDataDefaultFieldHandler
+ ]
+})
+export class AddonModDataModule {
+ constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModDataModuleHandler,
+ prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModDataPrefetchHandler,
+ contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModDataLinkHandler,
+ cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler,
+ approveLinkHandler: AddonModDataApproveLinkHandler, deleteLinkHandler: AddonModDataDeleteLinkHandler,
+ showLinkHandler: AddonModDataShowLinkHandler, editLinkHandler: AddonModDataEditLinkHandler) {
+ moduleDelegate.registerHandler(moduleHandler);
+ prefetchDelegate.registerHandler(prefetchHandler);
+ contentLinksDelegate.registerHandler(linkHandler);
+ contentLinksDelegate.registerHandler(approveLinkHandler);
+ contentLinksDelegate.registerHandler(deleteLinkHandler);
+ contentLinksDelegate.registerHandler(showLinkHandler);
+ contentLinksDelegate.registerHandler(editLinkHandler);
+ cronDelegate.register(syncHandler);
+ }
+}
diff --git a/src/addon/mod/data/data.scss b/src/addon/mod/data/data.scss
new file mode 100644
index 000000000..75b6d30ae
--- /dev/null
+++ b/src/addon/mod/data/data.scss
@@ -0,0 +1,101 @@
+.addon-data-contents {
+ overflow: visible;
+ white-space: normal;
+ word-break: break-word;
+ padding: $content-padding;
+ background-color: white;
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+ border-right-width: 0;
+ border-left-width: 0;
+ border-style: solid;
+ border-color: $list-border-color;
+
+ table, tbody {
+ display: block;
+ }
+
+ tr {
+ @extend .row;
+ padding: 0;
+ }
+
+ td, th {
+ @extend .col;
+ }
+}
+
+page-addon-mod-data-search,
+page-addon-mod-data-edit {
+ table {
+ width: 100%;
+ }
+ td {
+ vertical-align: top;
+ }
+
+ .item.item-input.item-block .item-inner ion-input,
+ .item.item-input.item-input-has-focus .item-inner ion-input,
+ .item.item-input.input-has-focus .item-inner ion-input {
+ border: 0 !important;
+ box-shadow: none;
+ }
+
+ .addon-data-lantlong {
+ display: flex;
+ }
+
+ form, .addon-data-advanced-search {
+ background-color: $list-background-color;
+
+ .core-mark-required {
+ float: right;
+
+ + ion-input,
+ + ion-select {
+ padding-right: 20px;
+ }
+ }
+
+ @if ($text-input-md-show-focus-highlight) {
+ .input-md input:focus {
+ @include md-input-highlight($text-input-md-highlight-color);
+ }
+ }
+
+ .input-md input {
+ @include padding-horizontal(null, ($item-md-padding-end / 2));
+ border-bottom: 1px solid $list-md-border-color;
+ &:focus {
+ @include md-input-highlight($text-input-md-highlight-color);
+ }
+ }
+
+ .input-ios input {
+ @include padding-horizontal(null, $item-ios-padding-end / 2);
+ @include safe-area-padding-horizontal(null, $item-ios-padding-end / 2);
+ border-bottom: $hairlines-width solid $list-ios-border-color;
+ &:focus {
+ @include ios-input-highlight($text-input-ios-highlight-color);
+ }
+ }
+
+ .input-wp input {
+ @include padding-horizontal(null, ($item-wp-padding-end / 2));
+ border-bottom: 1px solid $list-wp-border-color;
+ &:focus {
+ border-color: $text-input-wp-highlight-color;
+ }
+ }
+
+ ion-select {
+ width: 100%;
+ left: 0;
+ max-width: none;
+ }
+
+ .core-item-has-rich-text-editor {
+ margin-right: 1px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/checkbox/checkbox.module.ts b/src/addon/mod/data/fields/checkbox/checkbox.module.ts
new file mode 100644
index 000000000..5ac3377f2
--- /dev/null
+++ b/src/addon/mod/data/fields/checkbox/checkbox.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldCheckboxHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldCheckboxComponent } from './component/checkbox';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldCheckboxComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldCheckboxHandler
+ ],
+ exports: [
+ AddonModDataFieldCheckboxComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldCheckboxComponent
+ ]
+})
+export class AddonModDataFieldCheckboxModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldCheckboxHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/checkbox/component/checkbox.html b/src/addon/mod/data/fields/checkbox/component/checkbox.html
new file mode 100644
index 000000000..8006a3fdb
--- /dev/null
+++ b/src/addon/mod/data/fields/checkbox/component/checkbox.html
@@ -0,0 +1,15 @@
+
+
+
+ {{option.key}}
+
+
+
+
+ {{ 'addon.mod_data.selectedrequired' | translate }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/checkbox/component/checkbox.ts b/src/addon/mod/data/fields/checkbox/component/checkbox.ts
new file mode 100644
index 000000000..5d0ef6f35
--- /dev/null
+++ b/src/addon/mod/data/fields/checkbox/component/checkbox.ts
@@ -0,0 +1,73 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data checkbox field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-checkbox',
+ templateUrl: 'checkbox.html'
+})
+export class AddonModDataFieldCheckboxComponent extends AddonModDataFieldPluginComponent {
+
+ options = [];
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ this.updateValue(this.value);
+
+ return;
+ }
+
+ this.options = this.field.param1.split('\n').map((option) => {
+ return { key: option, value: option };
+ });
+
+ const values = [];
+ if (this.mode == 'edit' && this.value && this.value.content) {
+ this.value.content.split('##').forEach((value) => {
+ const x = this.options.findIndex((option) => value == option.key);
+ if (x >= 0) {
+ values.push(value);
+ }
+ });
+ }
+
+ if (this.mode == 'search') {
+ this.addControl('f_' + this.field.id + '_allreq');
+ }
+
+ this.addControl('f_' + this.field.id, values);
+ }
+
+ /**
+ * Update value being shown.
+ *
+ * @param {any} value New value to be set.
+ */
+ protected updateValue(value: any): void {
+ this.value = value;
+ this.value.content = value && value.content && value.content.split('##').join(' ');
+ }
+}
diff --git a/src/addon/mod/data/fields/checkbox/providers/handler.ts b/src/addon/mod/data/fields/checkbox/providers/handler.ts
new file mode 100644
index 000000000..48c50d8d7
--- /dev/null
+++ b/src/addon/mod/data/fields/checkbox/providers/handler.ts
@@ -0,0 +1,146 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldCheckboxComponent } from '../component/checkbox';
+
+/**
+ * Handler for checkbox data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldCheckboxHandler';
+ type = 'checkbox';
+
+ constructor(private translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldCheckboxComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id,
+ reqName = 'f_' + field.id + '_allreq';
+
+ const values = [];
+
+ if (inputData[fieldName] && inputData[fieldName].length > 0) {
+ values.push({
+ name: fieldName,
+ value: inputData[fieldName]
+ });
+
+ if (inputData[reqName]) {
+ values.push({
+ name: reqName,
+ value: true
+ });
+ }
+
+ return values;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName] && inputData[fieldName].length > 0) {
+ return [{
+ fieldid: field.id,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id;
+
+ originalFieldData = (originalFieldData && originalFieldData.content) || '';
+
+ return inputData[fieldName].join('##') != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = (offlineContent[''] && offlineContent[''].join('##')) || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/date/component/date.html b/src/addon/mod/data/fields/date/component/date.html
new file mode 100644
index 000000000..9d69823cc
--- /dev/null
+++ b/src/addon/mod/data/fields/date/component/date.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ {{ 'addon.mod_data.usedate' | translate }}
+
+
+
+
+
+
+
diff --git a/src/addon/mod/data/fields/date/component/date.ts b/src/addon/mod/data/fields/date/component/date.ts
new file mode 100644
index 000000000..c2c368be8
--- /dev/null
+++ b/src/addon/mod/data/fields/date/component/date.ts
@@ -0,0 +1,58 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { CoreTimeUtilsProvider } from '@providers/utils/time';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data date field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-date',
+ templateUrl: 'date.html'
+})
+export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginComponent {
+
+ format: string;
+
+ constructor(protected fb: FormBuilder, protected timeUtils: CoreTimeUtilsProvider) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ return;
+ }
+
+ let val;
+ this.format = this.timeUtils.getLocalizedDateFormat('LL');
+
+ if (this.mode == 'search') {
+ this.addControl('f_' + this.field.id + '_z');
+ val = this.search['f_' + this.field.id + '_y'] ? new Date(this.search['f_' + this.field.id + '_y'] + '-' +
+ this.search['f_' + this.field.id + '_m'] + '-' + this.search['f_' + this.field.id + '_d']) : new Date();
+
+ this.search['f_' + this.field.id] = val.toISOString();
+ } else {
+ val = this.value && this.value.content ? new Date(parseInt(this.value.content, 10) * 1000) : new Date();
+ val = val.toISOString();
+ }
+
+ this.addControl('f_' + this.field.id, val);
+ }
+}
diff --git a/src/addon/mod/data/fields/date/date.module.ts b/src/addon/mod/data/fields/date/date.module.ts
new file mode 100644
index 000000000..faf696cf5
--- /dev/null
+++ b/src/addon/mod/data/fields/date/date.module.ts
@@ -0,0 +1,51 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldDateHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldDateComponent } from './component/date';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { CorePipesModule } from '@pipes/pipes.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldDateComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ CorePipesModule
+ ],
+ providers: [
+ AddonModDataFieldDateHandler
+ ],
+ exports: [
+ AddonModDataFieldDateComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldDateComponent
+ ]
+})
+export class AddonModDataFieldDateModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldDateHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/date/providers/handler.ts b/src/addon/mod/data/fields/date/providers/handler.ts
new file mode 100644
index 000000000..f36d166b7
--- /dev/null
+++ b/src/addon/mod/data/fields/date/providers/handler.ts
@@ -0,0 +1,180 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldDateComponent } from '../component/date';
+
+/**
+ * Handler for date data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldDateHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldDateHandler';
+ type = 'date';
+
+ constructor(private translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldDateComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id,
+ enabledName = 'f_' + field.id + '_z';
+
+ if (inputData[enabledName] && typeof inputData[fieldName] == 'string') {
+ const values = [],
+ date = inputData[fieldName].substr(0, 10).split('-'),
+ year = date[0],
+ month = date[1],
+ day = date[2];
+ values.push({
+ name: fieldName + '_y',
+ value: year
+ });
+ values.push({
+ name: fieldName + '_m',
+ value: month
+ });
+ values.push({
+ name: fieldName + '_d',
+ value: day
+ });
+ values.push({
+ name: enabledName,
+ value: 1
+ });
+
+ return values;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (typeof inputData[fieldName] == 'string') {
+ const values = [],
+ date = inputData[fieldName].substr(0, 10).split('-'),
+ year = date[0],
+ month = date[1],
+ day = date[2];
+ values.push({
+ fieldid: field.id,
+ subfield: 'year',
+ value: year
+ });
+ values.push({
+ fieldid: field.id,
+ subfield: 'month',
+ value: month
+ });
+ values.push({
+ fieldid: field.id,
+ subfield: 'day',
+ value: day
+ });
+
+ return values;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id,
+ input = inputData[fieldName] && inputData[fieldName].substr(0, 10) || '';
+
+ originalFieldData = (originalFieldData && originalFieldData.content &&
+ new Date(originalFieldData.content * 1000).toISOString().substr(0, 10)) || '';
+
+ return input != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required &&
+ (!inputData || inputData.length < 2 || !inputData[0].value || !inputData[1].value || !inputData[2].value)) {
+
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ let date = Date.UTC(offlineContent['year'] || '', offlineContent['month'] ? offlineContent['month'] - 1 : null,
+ offlineContent['day'] || null);
+ date = Math.floor(date / 1000);
+
+ originalContent.content = date || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/field.module.ts b/src/addon/mod/data/fields/field.module.ts
new file mode 100644
index 000000000..3bd3d9e3d
--- /dev/null
+++ b/src/addon/mod/data/fields/field.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { AddonModDataFieldCheckboxModule } from './checkbox/checkbox.module';
+import { AddonModDataFieldDateModule } from './date/date.module';
+import { AddonModDataFieldFileModule } from './file/file.module';
+import { AddonModDataFieldLatlongModule } from './latlong/latlong.module';
+import { AddonModDataFieldMenuModule } from './menu/menu.module';
+import { AddonModDataFieldMultimenuModule } from './multimenu/multimenu.module';
+import { AddonModDataFieldNumberModule } from './number/number.module';
+import { AddonModDataFieldPictureModule } from './picture/picture.module';
+import { AddonModDataFieldRadiobuttonModule } from './radiobutton/radiobutton.module';
+import { AddonModDataFieldTextModule } from './text/text.module';
+import { AddonModDataFieldTextareaModule } from './textarea/textarea.module';
+import { AddonModDataFieldUrlModule } from './url/url.module';
+
+@NgModule({
+ declarations: [],
+ imports: [
+ AddonModDataFieldCheckboxModule,
+ AddonModDataFieldDateModule,
+ AddonModDataFieldFileModule,
+ AddonModDataFieldLatlongModule,
+ AddonModDataFieldMenuModule,
+ AddonModDataFieldMultimenuModule,
+ AddonModDataFieldNumberModule,
+ AddonModDataFieldPictureModule,
+ AddonModDataFieldRadiobuttonModule,
+ AddonModDataFieldTextModule,
+ AddonModDataFieldTextareaModule,
+ AddonModDataFieldUrlModule
+ ],
+ providers: [
+ ],
+ exports: []
+})
+export class AddonModDataFieldModule { }
diff --git a/src/addon/mod/data/fields/file/component/file.html b/src/addon/mod/data/fields/file/component/file.html
new file mode 100644
index 000000000..9b6dd2791
--- /dev/null
+++ b/src/addon/mod/data/fields/file/component/file.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/mod/data/fields/file/component/file.ts b/src/addon/mod/data/fields/file/component/file.ts
new file mode 100644
index 000000000..e39faafe5
--- /dev/null
+++ b/src/addon/mod/data/fields/file/component/file.ts
@@ -0,0 +1,83 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+import { CoreFileSessionProvider } from '@providers/file-session';
+import { AddonModDataProvider } from '../../../providers/data';
+
+/**
+ * Component to render data file field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-file',
+ templateUrl: 'file.html'
+})
+export class AddonModDataFieldFileComponent extends AddonModDataFieldPluginComponent {
+
+ files = [];
+ component: string;
+ componentId: number;
+ maxSizeBytes: number;
+
+ constructor(protected fb: FormBuilder, private fileSessionprovider: CoreFileSessionProvider) {
+ super(fb);
+ }
+
+ /**
+ * Get the files from the input value.
+ *
+ * @param {any} value Input value.
+ * @return {any} List of files.
+ */
+ protected getFiles(value: any): any {
+ let files = (value && value.files) || [];
+
+ // Reduce to first element.
+ if (files.length > 0) {
+ files = [files[0]];
+ }
+
+ return files;
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.mode != 'search') {
+ this.component = AddonModDataProvider.COMPONENT;
+ this.componentId = this.database.coursemodule;
+
+ this.updateValue(this.value);
+
+ if (this.mode == 'edit') {
+ this.maxSizeBytes = parseInt(this.field.param3, 10);
+ this.fileSessionprovider.setFiles(this.component, this.database.id + '_' + this.field.id, this.files);
+ }
+ } else {
+ this.addControl('f_' + this.field.id);
+ }
+ }
+
+ /**
+ * Update value being shown.
+ *
+ * @param {any} value New value to be set.
+ */
+ protected updateValue(value: any): void {
+ this.value = value;
+ this.files = this.getFiles(value);
+ }
+}
diff --git a/src/addon/mod/data/fields/file/file.module.ts b/src/addon/mod/data/fields/file/file.module.ts
new file mode 100644
index 000000000..b2d240329
--- /dev/null
+++ b/src/addon/mod/data/fields/file/file.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldFileHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldFileComponent } from './component/file';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldFileComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ ],
+ providers: [
+ AddonModDataFieldFileHandler
+ ],
+ exports: [
+ AddonModDataFieldFileComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldFileComponent
+ ]
+})
+export class AddonModDataFieldFileModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldFileHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/file/providers/handler.ts b/src/addon/mod/data/fields/file/providers/handler.ts
new file mode 100644
index 000000000..38a58e7de
--- /dev/null
+++ b/src/addon/mod/data/fields/file/providers/handler.ts
@@ -0,0 +1,158 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { CoreFileSessionProvider } from '@providers/file-session';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataProvider } from '../../../providers/data';
+import { AddonModDataFieldFileComponent } from '../component/file';
+import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
+
+/**
+ * Handler for file data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldFileHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldFileHandler';
+ type = 'file';
+
+ constructor(private translate: TranslateService, private fileSessionprovider: CoreFileSessionProvider,
+ private fileUploaderProvider: CoreFileUploaderProvider) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldFileComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ name: fieldName,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const files = this.getFieldEditFiles(field);
+
+ if (files.length) {
+ return [{
+ fieldid: field.id,
+ subfield: 'file',
+ files: files
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit files in the input data.
+ *
+ * @param {any} field Defines the field..
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditFiles(field: any): any {
+ return this.fileSessionprovider.getFiles(AddonModDataProvider.COMPONENT, field.dataid + '_' + field.id);
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const files = this.fileSessionprovider.getFiles(AddonModDataProvider.COMPONENT, field.dataid + '_' + field.id) || [];
+ let originalFiles = (originalFieldData && originalFieldData.files) || [];
+
+ if (originalFiles.length) {
+ originalFiles = [originalFiles[0]];
+ }
+
+ return this.fileUploaderProvider.areFileListDifferent(files, originalFiles);
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ if (offlineContent && offlineContent.file && offlineContent.file.offline > 0 && offlineFiles && offlineFiles.length > 0) {
+ originalContent.content = offlineFiles[0].filename;
+ originalContent.files = [offlineFiles[0]];
+ } else if (offlineContent && offlineContent.file && offlineContent.file.online && offlineContent.file.online.length > 0) {
+ originalContent.content = offlineContent.file.online[0].filename;
+ originalContent.files = [offlineContent.file.online[0]];
+ }
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/latlong/component/latlong.html b/src/addon/mod/data/fields/latlong/component/latlong.html
new file mode 100644
index 000000000..a219e554f
--- /dev/null
+++ b/src/addon/mod/data/fields/latlong/component/latlong.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ °N
+
+
+
+ °E
+
+
+
+
+
+
+ {{ formatLatLong(north, east) }}
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts
new file mode 100644
index 000000000..2b7fda2be
--- /dev/null
+++ b/src/addon/mod/data/fields/latlong/component/latlong.ts
@@ -0,0 +1,97 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { Platform } from 'ionic-angular';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data latlong field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-latlong',
+ templateUrl: 'latlong.html'
+})
+export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginComponent {
+
+ north: number;
+ east: number;
+
+ constructor(protected fb: FormBuilder, private platform: Platform) {
+ super(fb);
+ }
+
+ /**
+ * Format latitude and longitude in a simple text.
+ *
+ * @param {number} north Degrees north.
+ * @param {number} east Degrees East.
+ * @return {string} Readable Latitude and logitude.
+ */
+ formatLatLong(north: number, east: number): string {
+ if (north !== null || east !== null) {
+ const northFixed = north ? Math.abs(north).toFixed(4) : '0.0000',
+ eastFixed = east ? Math.abs(east).toFixed(4) : '0.0000';
+
+ return northFixed + (north < 0 ? '°S' : '°N') + ' ' + eastFixed + (east < 0 ? '°W' : '°E');
+ }
+ }
+
+ /**
+ * Get link to maps from latitude and longitude.
+ *
+ * @param {number} north Degrees north.
+ * @param {number} east Degrees East.
+ * @return {string} Link to maps depending on platform.
+ */
+ getLatLongLink(north: number, east: number): string {
+ if (north !== null || east !== null) {
+ const northFixed = north ? north.toFixed(4) : '0.0000',
+ eastFixed = east ? east.toFixed(4) : '0.0000';
+
+ if (this.platform.is('ios')) {
+ return 'http://maps.apple.com/?ll=' + northFixed + ',' + eastFixed + '&near=' + northFixed + ',' + eastFixed;
+ }
+
+ return 'geo:' + northFixed + ',' + eastFixed;
+ }
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.value) {
+ this.updateValue(this.value);
+ }
+
+ if (this.mode == 'edit') {
+ this.addControl('f_' + this.field.id + '_0', this.north);
+ this.addControl('f_' + this.field.id + '_1', this.east);
+ } else if (this.mode == 'search') {
+ this.addControl('f_' + this.field.id);
+ }
+ }
+
+ /**
+ * Update value being shown.
+ *
+ * @param {any} value New value to be set.
+ */
+ protected updateValue(value: any): void {
+ this.value = value;
+ this.north = (value && parseFloat(value.content)) || null;
+ this.east = (value && parseFloat(value.content1)) || null;
+ }
+}
diff --git a/src/addon/mod/data/fields/latlong/latlong.module.ts b/src/addon/mod/data/fields/latlong/latlong.module.ts
new file mode 100644
index 000000000..5b9b4f11a
--- /dev/null
+++ b/src/addon/mod/data/fields/latlong/latlong.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldLatlongHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldLatlongComponent } from './component/latlong';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldLatlongComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldLatlongHandler
+ ],
+ exports: [
+ AddonModDataFieldLatlongComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldLatlongComponent
+ ]
+})
+export class AddonModDataFieldLatlongModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldLatlongHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/latlong/providers/handler.ts b/src/addon/mod/data/fields/latlong/providers/handler.ts
new file mode 100644
index 000000000..fb549c7c4
--- /dev/null
+++ b/src/addon/mod/data/fields/latlong/providers/handler.ts
@@ -0,0 +1,159 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldLatlongComponent } from '../component/latlong';
+
+/**
+ * Handler for latlong data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldLatlongHandler';
+ type = 'latlong';
+
+ constructor(private translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldLatlongComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ name: fieldName,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id,
+ values = [];
+
+ if (inputData[fieldName + '_0']) {
+ values.push({
+ fieldid: field.id,
+ subfield: '0',
+ value: inputData[fieldName + '_0']
+ });
+ }
+
+ if (inputData[fieldName + '_1']) {
+ values.push({
+ fieldid: field.id,
+ subfield: '1',
+ value: inputData[fieldName + '_1']
+ });
+ }
+
+ return values;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id,
+ lat = inputData[fieldName + '_0'] || '',
+ long = inputData[fieldName + '_1'] || '',
+ originalLat = (originalFieldData && originalFieldData.content) || '',
+ originalLong = (originalFieldData && originalFieldData.content1) || '';
+
+ return lat != originalLat || long != originalLong;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ let valueCount = 0;
+
+ // The lat long class has two values that need to be checked.
+ inputData.forEach((value) => {
+ if (typeof value.value != 'undefined' && value.value != '') {
+ valueCount++;
+ }
+ });
+
+ // If we get here then only one field has been filled in.
+ if (valueCount == 1) {
+ return this.translate.instant('addon.mod_data.latlongboth');
+ } else if (field.required && valueCount == 0) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = offlineContent[0] || '';
+ originalContent.content1 = offlineContent[1] || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/menu/component/menu.html b/src/addon/mod/data/fields/menu/component/menu.html
new file mode 100644
index 000000000..9a9357500
--- /dev/null
+++ b/src/addon/mod/data/fields/menu/component/menu.html
@@ -0,0 +1,10 @@
+
+
+
+ {{ 'addon.mod_data.menuchoose' | translate }}
+ {{option}}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/menu/component/menu.ts b/src/addon/mod/data/fields/menu/component/menu.ts
new file mode 100644
index 000000000..e722375ef
--- /dev/null
+++ b/src/addon/mod/data/fields/menu/component/menu.ts
@@ -0,0 +1,50 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data menu field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-menu',
+ templateUrl: 'menu.html'
+})
+export class AddonModDataFieldMenuComponent extends AddonModDataFieldPluginComponent {
+
+ options = [];
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ return;
+ }
+
+ this.options = this.field.param1.split('\n');
+
+ let val;
+ if (this.mode == 'edit' && this.value) {
+ val = this.value.content;
+ }
+
+ this.addControl('f_' + this.field.id, val);
+ }
+}
diff --git a/src/addon/mod/data/fields/menu/menu.module.ts b/src/addon/mod/data/fields/menu/menu.module.ts
new file mode 100644
index 000000000..2c4a16427
--- /dev/null
+++ b/src/addon/mod/data/fields/menu/menu.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldMenuHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldMenuComponent } from './component/menu';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldMenuComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldMenuHandler
+ ],
+ exports: [
+ AddonModDataFieldMenuComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldMenuComponent
+ ]
+})
+export class AddonModDataFieldMenuModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldMenuHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/menu/providers/handler.ts b/src/addon/mod/data/fields/menu/providers/handler.ts
new file mode 100644
index 000000000..e8427cc66
--- /dev/null
+++ b/src/addon/mod/data/fields/menu/providers/handler.ts
@@ -0,0 +1,133 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldMenuComponent } from '../component/menu';
+
+/**
+ * Handler for menu data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldMenuHandler';
+ type = 'menu';
+
+ constructor(private translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldMenuComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id;
+ if (inputData[fieldName]) {
+ return [{
+ name: fieldName,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ fieldid: field.id,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id,
+ input = inputData[fieldName] || '';
+ originalFieldData = (originalFieldData && originalFieldData.content) || '';
+
+ return input != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = offlineContent[''] || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/multimenu/component/multimenu.html b/src/addon/mod/data/fields/multimenu/component/multimenu.html
new file mode 100644
index 000000000..e1d9081ba
--- /dev/null
+++ b/src/addon/mod/data/fields/multimenu/component/multimenu.html
@@ -0,0 +1,16 @@
+
+
+
+ {{option.key}}
+
+
+
+
+
+ {{ 'addon.mod_data.selectedrequired' | translate }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/multimenu/component/multimenu.ts b/src/addon/mod/data/fields/multimenu/component/multimenu.ts
new file mode 100644
index 000000000..a93ee67d5
--- /dev/null
+++ b/src/addon/mod/data/fields/multimenu/component/multimenu.ts
@@ -0,0 +1,73 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data multimenu field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-multimenu',
+ templateUrl: 'multimenu.html'
+})
+export class AddonModDataFieldMultimenuComponent extends AddonModDataFieldPluginComponent {
+
+ options = [];
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ this.updateValue(this.value);
+
+ return;
+ }
+
+ this.options = this.field.param1.split('\n').map((option) => {
+ return { key: option, value: option };
+ });
+
+ const values = [];
+ if (this.mode == 'edit' && this.value && this.value.content) {
+ this.value.content.split('##').forEach((value) => {
+ const x = this.options.findIndex((option) => value == option.key);
+ if (x >= 0) {
+ values.push(value);
+ }
+ });
+ }
+
+ if (this.mode == 'search') {
+ this.addControl('f_' + this.field.id + '_allreq');
+ }
+
+ this.addControl('f_' + this.field.id, values);
+ }
+
+ /**
+ * Update value being shown.
+ *
+ * @param {any} value New value to be set.
+ */
+ protected updateValue(value: any): void {
+ this.value = value;
+ this.value.content = value && value.content && value.content.split('##').join(' ');
+ }
+}
diff --git a/src/addon/mod/data/fields/multimenu/multimenu.module.ts b/src/addon/mod/data/fields/multimenu/multimenu.module.ts
new file mode 100644
index 000000000..7b6b72d12
--- /dev/null
+++ b/src/addon/mod/data/fields/multimenu/multimenu.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldMultimenuHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldMultimenuComponent } from './component/multimenu';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldMultimenuComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldMultimenuHandler
+ ],
+ exports: [
+ AddonModDataFieldMultimenuComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldMultimenuComponent
+ ]
+})
+export class AddonModDataFieldMultimenuModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldMultimenuHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/multimenu/providers/handler.ts b/src/addon/mod/data/fields/multimenu/providers/handler.ts
new file mode 100644
index 000000000..716da06bf
--- /dev/null
+++ b/src/addon/mod/data/fields/multimenu/providers/handler.ts
@@ -0,0 +1,146 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldMultimenuComponent } from '../component/multimenu';
+
+/**
+ * Handler for multimenu data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldMultimenuHandler';
+ type = 'multimenu';
+
+ constructor(private translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldMultimenuComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id,
+ reqName = 'f_' + field.id + '_allreq';
+
+ if (inputData[fieldName] && inputData[fieldName].length > 0) {
+ const values = [];
+
+ values.push({
+ name: fieldName,
+ value: inputData[fieldName]
+ });
+
+ if (inputData[reqName]) {
+ values.push({
+ name: reqName,
+ value: true
+ });
+ }
+
+ return values;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName] && inputData[fieldName].length > 0) {
+ return [{
+ fieldid: field.id,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id;
+
+ originalFieldData = (originalFieldData && originalFieldData.content) || '';
+
+ return inputData[fieldName].join('##') != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = (offlineContent[''] && offlineContent[''].join('###')) || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/number/component/number.html b/src/addon/mod/data/fields/number/component/number.html
new file mode 100644
index 000000000..aa71edc43
--- /dev/null
+++ b/src/addon/mod/data/fields/number/component/number.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/addon/mod/data/fields/number/component/number.ts b/src/addon/mod/data/fields/number/component/number.ts
new file mode 100644
index 000000000..c7dcb1127
--- /dev/null
+++ b/src/addon/mod/data/fields/number/component/number.ts
@@ -0,0 +1,47 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data number field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-number',
+ templateUrl: 'number.html'
+})
+export class AddonModDataFieldNumberComponent extends AddonModDataFieldPluginComponent{
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ return;
+ }
+
+ let value;
+ if (this.mode == 'edit' && this.value) {
+ const v = parseFloat(this.value.content);
+ value = isNaN(v) ? '' : v;
+ }
+
+ this.addControl('f_' + this.field.id, value);
+ }
+}
diff --git a/src/addon/mod/data/fields/number/number.module.ts b/src/addon/mod/data/fields/number/number.module.ts
new file mode 100644
index 000000000..ce2acc789
--- /dev/null
+++ b/src/addon/mod/data/fields/number/number.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldNumberHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldNumberComponent } from './component/number';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldNumberComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldNumberHandler
+ ],
+ exports: [
+ AddonModDataFieldNumberComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldNumberComponent
+ ]
+})
+export class AddonModDataFieldNumberModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldNumberHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/number/providers/handler.ts b/src/addon/mod/data/fields/number/providers/handler.ts
new file mode 100644
index 000000000..40663e8f0
--- /dev/null
+++ b/src/addon/mod/data/fields/number/providers/handler.ts
@@ -0,0 +1,75 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldTextHandler } from '../../text/providers/handler';
+import { AddonModDataFieldNumberComponent } from '../component/number';
+
+/**
+ * Handler for number data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldNumberHandler extends AddonModDataFieldTextHandler {
+ name = 'AddonModDataFieldNumberHandler';
+ type = 'number';
+
+ constructor(protected translate: TranslateService) {
+ super(translate);
+ }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldNumberComponent;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id,
+ input = typeof inputData[fieldName] != 'undefined' ? parseFloat(inputData[fieldName]) : '';
+
+ originalFieldData = (originalFieldData && typeof originalFieldData.content != 'undefined') ?
+ parseFloat(originalFieldData.content) : '';
+
+ return input != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || inputData[0].value == '')) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+}
diff --git a/src/addon/mod/data/fields/picture/component/picture.html b/src/addon/mod/data/fields/picture/component/picture.html
new file mode 100644
index 000000000..ee6cdcad0
--- /dev/null
+++ b/src/addon/mod/data/fields/picture/component/picture.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ {{ 'addon.mod_data.alttext' | translate }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/mod/data/fields/picture/component/picture.ts b/src/addon/mod/data/fields/picture/component/picture.ts
new file mode 100644
index 000000000..5c6796d90
--- /dev/null
+++ b/src/addon/mod/data/fields/picture/component/picture.ts
@@ -0,0 +1,136 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+import { CoreFileSessionProvider } from '@providers/file-session';
+import { AddonModDataProvider } from '../../../providers/data';
+
+/**
+ * Component to render data picture field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-picture',
+ templateUrl: 'picture.html'
+})
+export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginComponent {
+
+ files = [];
+ component: string;
+ componentId: number;
+ maxSizeBytes: number;
+
+ image: any;
+ entryId: number;
+ imageUrl: string;
+ title: string;
+ width: string;
+ height: string;
+
+ constructor(protected fb: FormBuilder, private fileSessionprovider: CoreFileSessionProvider) {
+ super(fb);
+ }
+
+ /**
+ * Get the files from the input value.
+ *
+ * @param {any} value Input value.
+ * @return {any} List of files.
+ */
+ protected getFiles(value: any): any {
+ let files = (value && value.files) || [];
+
+ // Reduce to first element.
+ if (files.length > 0) {
+ files = [files[0]];
+ }
+
+ return files;
+ }
+
+ /**
+ * Find file in a list.
+ *
+ * @param {any[]} files File list where to search.
+ * @param {string} filenameSeek Filename to search.
+ * @return {any} File found or false.
+ */
+ protected findFile(files: any[], filenameSeek: string): any {
+ return files.find((file) => file.filename == filenameSeek) || false;
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.mode != 'search') {
+ this.component = AddonModDataProvider.COMPONENT;
+ this.componentId = this.database.coursemodule;
+
+ this.updateValue(this.value);
+
+ if (this.mode == 'edit') {
+ this.maxSizeBytes = parseInt(this.field.param3, 10);
+ this.fileSessionprovider.setFiles(this.component, this.database.id + '_' + this.field.id, this.files);
+
+ const alttext = (this.value && this.value.content1) || '';
+ this.addControl('f_' + this.field.id + '_alttext', alttext);
+ }
+ } else {
+ this.addControl('f_' + this.field.id);
+ }
+ }
+
+ /**
+ * Update value being shown.
+ *
+ * @param {any} value New value to be set.
+ */
+ protected updateValue(value: any): void {
+ this.value = value;
+
+ // Edit mode, the list shouldn't change so there is no need to watch it.
+ const files = value && value.files || [];
+
+ // Get image or thumb.
+ if (files.length > 0) {
+ const filenameSeek = this.mode == 'list' ? 'thumb_' + value.content : value.content;
+ this.image = this.findFile(files, filenameSeek);
+
+ if (!this.image && this.mode == 'list') {
+ this.image = this.findFile(files, value.content);
+ }
+
+ this.files = [this.image];
+ } else {
+ this.image = false;
+ this.files = [];
+ }
+
+ if (this.mode != 'edit') {
+ this.entryId = (value && value.recordid) || null;
+ this.title = (value && value.content1) || '';
+ this.imageUrl = null;
+ if (this.image) {
+ if (this.image.offline) {
+ this.imageUrl = (this.image && this.image.toURL()) || null;
+ } else {
+ this.imageUrl = (this.image && this.image.fileurl) || null;
+ }
+ }
+ this.width = this.field.param1 || '';
+ this.height = this.field.param2 || '';
+ }
+ }
+}
diff --git a/src/addon/mod/data/fields/picture/picture.module.ts b/src/addon/mod/data/fields/picture/picture.module.ts
new file mode 100644
index 000000000..c666e1bad
--- /dev/null
+++ b/src/addon/mod/data/fields/picture/picture.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldPictureHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldPictureComponent } from './component/picture';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldPictureComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ ],
+ providers: [
+ AddonModDataFieldPictureHandler
+ ],
+ exports: [
+ AddonModDataFieldPictureComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldPictureComponent
+ ]
+})
+export class AddonModDataFieldPictureModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldPictureHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/picture/providers/handler.ts b/src/addon/mod/data/fields/picture/providers/handler.ts
new file mode 100644
index 000000000..a5eab43b0
--- /dev/null
+++ b/src/addon/mod/data/fields/picture/providers/handler.ts
@@ -0,0 +1,194 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { CoreFileSessionProvider } from '@providers/file-session';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataProvider } from '../../../providers/data';
+import { AddonModDataFieldPictureComponent } from '../component/picture';
+import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
+
+/**
+ * Handler for picture data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldPictureHandler';
+ type = 'picture';
+
+ constructor(private translate: TranslateService, private fileSessionprovider: CoreFileSessionProvider,
+ private fileUploaderProvider: CoreFileUploaderProvider) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldPictureComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ name: fieldName,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const files = this.getFieldEditFiles(field),
+ values = [],
+ fieldName = 'f_' + field.id + '_alttext';
+
+ if (files.length) {
+ values.push({
+ fieldid: field.id,
+ subfield: 'file',
+ files: files
+ });
+ }
+
+ if (inputData[fieldName]) {
+ values.push({
+ fieldid: field.id,
+ subfield: 'alttext',
+ value: inputData[fieldName]
+ });
+ }
+
+ return values;
+ }
+
+ /**
+ * Get field edit files in the input data.
+ *
+ * @param {any} field Defines the field..
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditFiles(field: any): any {
+ return this.fileSessionprovider.getFiles(AddonModDataProvider.COMPONENT, field.dataid + '_' + field.id);
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id + '_alttext',
+ altText = inputData[fieldName] || '',
+ originalAltText = (originalFieldData && originalFieldData.content1) || '',
+ files = this.getFieldEditFiles(field) || [];
+ let originalFiles = (originalFieldData && originalFieldData.files) || [];
+
+ // Get image.
+ if (originalFiles.length > 0) {
+ const filenameSeek = (originalFieldData && originalFieldData.content) || '',
+ file = originalFiles.find((file) => file.filename == filenameSeek);
+ if (file) {
+ originalFiles = [file];
+ }
+ } else {
+ originalFiles = [];
+ }
+
+ return altText != originalAltText || this.fileUploaderProvider.areFileListDifferent(files, originalFiles);
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required) {
+ if (!inputData || !inputData.length) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ const found = inputData.some((input) => {
+ if (typeof input.subfield != 'undefined' && input.subfield == 'file') {
+ return !!input.value;
+ }
+
+ return false;
+ });
+
+ if (!found) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ if (offlineContent && offlineContent.file && offlineContent.file.offline > 0 && offlineFiles && offlineFiles.length > 0) {
+ originalContent.content = offlineFiles[0].filename;
+ originalContent.files = [offlineFiles[0]];
+ } else if (offlineContent && offlineContent.file && offlineContent.file.online && offlineContent.file.online.length > 0) {
+ originalContent.content = offlineContent.file.online[0].filename;
+ originalContent.files = [offlineContent.file.online[0]];
+ }
+
+ originalContent.content1 = offlineContent.alttext || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/radiobutton/component/radiobutton.html b/src/addon/mod/data/fields/radiobutton/component/radiobutton.html
new file mode 100644
index 000000000..9a9357500
--- /dev/null
+++ b/src/addon/mod/data/fields/radiobutton/component/radiobutton.html
@@ -0,0 +1,10 @@
+
+
+
+ {{ 'addon.mod_data.menuchoose' | translate }}
+ {{option}}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts b/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts
new file mode 100644
index 000000000..e2b64425b
--- /dev/null
+++ b/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts
@@ -0,0 +1,50 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data radiobutton field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-radiobutton',
+ templateUrl: 'radiobutton.html'
+})
+export class AddonModDataFieldRadiobuttonComponent extends AddonModDataFieldPluginComponent {
+
+ options = [];
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ return;
+ }
+
+ this.options = this.field.param1.split('\n');
+
+ let val;
+ if (this.mode == 'edit' && this.value) {
+ val = this.value.content;
+ }
+
+ this.addControl('f_' + this.field.id, val);
+ }
+}
diff --git a/src/addon/mod/data/fields/radiobutton/providers/handler.ts b/src/addon/mod/data/fields/radiobutton/providers/handler.ts
new file mode 100644
index 000000000..6efbd744b
--- /dev/null
+++ b/src/addon/mod/data/fields/radiobutton/providers/handler.ts
@@ -0,0 +1,133 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldRadiobuttonComponent } from '../component/radiobutton';
+
+/**
+ * Handler for checkbox data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldRadiobuttonHandler';
+ type = 'radiobutton';
+
+ constructor(private translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldRadiobuttonComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id;
+ if (inputData[fieldName]) {
+ return [{
+ name: fieldName,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ fieldid: field.id,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id,
+ input = inputData[fieldName] || '';
+ originalFieldData = (originalFieldData && originalFieldData.content) || '';
+
+ return input != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = offlineContent[''] || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts b/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts
new file mode 100644
index 000000000..d361bc934
--- /dev/null
+++ b/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldRadiobuttonHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldRadiobuttonComponent } from './component/radiobutton';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldRadiobuttonComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldRadiobuttonHandler
+ ],
+ exports: [
+ AddonModDataFieldRadiobuttonComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldRadiobuttonComponent
+ ]
+})
+export class AddonModDataFieldRadiobuttonModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldRadiobuttonHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/text/component/text.html b/src/addon/mod/data/fields/text/component/text.html
new file mode 100644
index 000000000..fa8846daf
--- /dev/null
+++ b/src/addon/mod/data/fields/text/component/text.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/text/component/text.ts b/src/addon/mod/data/fields/text/component/text.ts
new file mode 100644
index 000000000..1d9523d51
--- /dev/null
+++ b/src/addon/mod/data/fields/text/component/text.ts
@@ -0,0 +1,46 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data text field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-text',
+ templateUrl: 'text.html'
+})
+export class AddonModDataFieldTextComponent extends AddonModDataFieldPluginComponent {
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ return;
+ }
+
+ let value;
+ if (this.mode == 'edit' && this.value) {
+ value = this.value.content;
+ }
+
+ this.addControl('f_' + this.field.id, value);
+ }
+}
diff --git a/src/addon/mod/data/fields/text/providers/handler.ts b/src/addon/mod/data/fields/text/providers/handler.ts
new file mode 100644
index 000000000..19d49b713
--- /dev/null
+++ b/src/addon/mod/data/fields/text/providers/handler.ts
@@ -0,0 +1,134 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
+import { AddonModDataFieldTextComponent } from '../component/text';
+
+/**
+ * Handler for number data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler {
+ name = 'AddonModDataFieldTextHandler';
+ type = 'text';
+
+ constructor(protected translate: TranslateService) { }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldTextComponent;
+ }
+
+ /**
+ * Get field search data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the search form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldSearchData(field: any, inputData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ name: fieldName,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return [{
+ fieldid: field.id,
+ value: inputData[fieldName]
+ }];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field data in changed.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {Promise | boolean} If the field has changes.
+ */
+ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean {
+ const fieldName = 'f_' + field.id,
+ input = inputData[fieldName] || '';
+ originalFieldData = (originalFieldData && originalFieldData.content) || '';
+
+ return input != originalFieldData;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = offlineContent[''] || '';
+
+ return originalContent;
+ }
+
+ /**
+ * Whether or not the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+}
diff --git a/src/addon/mod/data/fields/text/text.module.ts b/src/addon/mod/data/fields/text/text.module.ts
new file mode 100644
index 000000000..a96ddf9de
--- /dev/null
+++ b/src/addon/mod/data/fields/text/text.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldTextHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldTextComponent } from './component/text';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldTextComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldTextHandler
+ ],
+ exports: [
+ AddonModDataFieldTextComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldTextComponent
+ ]
+})
+export class AddonModDataFieldTextModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldTextHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/textarea/component/textarea.html b/src/addon/mod/data/fields/textarea/component/textarea.html
new file mode 100644
index 000000000..861d1edc9
--- /dev/null
+++ b/src/addon/mod/data/fields/textarea/component/textarea.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/textarea/component/textarea.ts b/src/addon/mod/data/fields/textarea/component/textarea.ts
new file mode 100644
index 000000000..532d762fa
--- /dev/null
+++ b/src/addon/mod/data/fields/textarea/component/textarea.ts
@@ -0,0 +1,68 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
+import { AddonModDataProvider } from '../../../providers/data';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data number field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-textarea',
+ templateUrl: 'textarea.html'
+})
+export class AddonModDataFieldTextareaComponent extends AddonModDataFieldPluginComponent {
+
+ component: string;
+ componentId: number;
+
+ constructor(protected fb: FormBuilder, protected textUtils: CoreTextUtilsProvider) {
+ super(fb);
+ }
+
+ /**
+ * Format value to be shown. Replacing plugin file Urls.
+ *
+ * @param {any} value Value to replace.
+ * @return {string} Replaced string to be rendered.
+ */
+ format(value: any): string {
+ const files = (value && value.files) || [];
+
+ return value ? this.textUtils.replacePluginfileUrls(value.content, files) : '';
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ this.component = AddonModDataProvider.COMPONENT;
+ this.componentId = this.database.coursemodule;
+
+ return;
+ }
+
+ let text;
+ // Check if rich text editor is enabled.
+ if (this.mode == 'edit') {
+ const files = (this.value && this.value.files) || [];
+ text = this.value ? this.textUtils.replacePluginfileUrls(this.value.content, files) : '';
+ }
+
+ this.addControl('f_' + this.field.id, text);
+ }
+}
diff --git a/src/addon/mod/data/fields/textarea/providers/handler.ts b/src/addon/mod/data/fields/textarea/providers/handler.ts
new file mode 100644
index 000000000..0a5829698
--- /dev/null
+++ b/src/addon/mod/data/fields/textarea/providers/handler.ts
@@ -0,0 +1,145 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldTextHandler } from '../../text/providers/handler';
+import { AddonModDataFieldTextareaComponent } from '../component/textarea';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
+
+/**
+ * Handler for textarea data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldTextareaHandler extends AddonModDataFieldTextHandler {
+ name = 'AddonModDataFieldTextareaHandler';
+ type = 'textarea';
+
+ constructor(protected translate: TranslateService, private textUtils: CoreTextUtilsProvider,
+ private domUtils: CoreDomUtilsProvider) {
+ super(translate);
+ }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldTextareaComponent;
+ }
+
+ /**
+ * Get field edit data in the input data.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditData(field: any, inputData: any, originalFieldData: any): any {
+ const fieldName = 'f_' + field.id;
+
+ if (inputData[fieldName]) {
+ return this.domUtils.isRichTextEditorEnabled().then((enabled) => {
+ const files = this.getFieldEditFiles(field, inputData, originalFieldData);
+ let text = this.textUtils.restorePluginfileUrls(inputData[fieldName], files);
+
+ if (!enabled) {
+ // Rich text editor not enabled, add some HTML to the text if needed.
+ text = this.textUtils.formatHtmlLines(text);
+ }
+
+ return [{
+ fieldid: field.id,
+ value: text
+ },
+ {
+ fieldid: field.id,
+ subfield: 'content1',
+ value: 1
+ },
+ {
+ fieldid: field.id,
+ subfield: 'itemid',
+ files: files
+ }
+ ];
+ });
+ }
+
+ return false;
+ }
+
+ /**
+ * Get field edit files in the input data.
+ *
+ * @param {any} field Defines the field..
+ * @param {any} inputData Data entered in the edit form.
+ * @param {any} originalFieldData Original field entered data.
+ * @return {any} With name and value of the data to be sent.
+ */
+ getFieldEditFiles(field: any, inputData: any, originalFieldData: any): any {
+ return (originalFieldData && originalFieldData.files) || [];
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required) {
+ if (!inputData || !inputData.length) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ const found = inputData.some((input) => {
+ if (!input.subfield) {
+ return !!input.value;
+ }
+
+ return false;
+ });
+
+ if (!found) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Override field content data with offline submission.
+ *
+ * @param {any} originalContent Original data to be overriden.
+ * @param {any} offlineContent Array with all the offline data to override.
+ * @param {any} [offlineFiles] Array with all the offline files in the field.
+ * @return {any} Data overriden
+ */
+ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any {
+ originalContent.content = offlineContent[''] || '';
+ if (originalContent.content.length > 0 && originalContent.files && originalContent.files.length > 0) {
+ // Take the original files since we cannot edit them on the app.
+ originalContent.content = this.textUtils.replacePluginfileUrls(originalContent.content, originalContent.files);
+ }
+
+ return originalContent;
+ }
+}
diff --git a/src/addon/mod/data/fields/textarea/textarea.module.ts b/src/addon/mod/data/fields/textarea/textarea.module.ts
new file mode 100644
index 000000000..8c63d53f7
--- /dev/null
+++ b/src/addon/mod/data/fields/textarea/textarea.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldTextareaHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldTextareaComponent } from './component/textarea';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldTextareaComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldTextareaHandler
+ ],
+ exports: [
+ AddonModDataFieldTextareaComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldTextareaComponent
+ ]
+})
+export class AddonModDataFieldTextareaModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldTextareaHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/fields/url/component/url.html b/src/addon/mod/data/fields/url/component/url.html
new file mode 100644
index 000000000..f1c018713
--- /dev/null
+++ b/src/addon/mod/data/fields/url/component/url.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+{{field.name}}
\ No newline at end of file
diff --git a/src/addon/mod/data/fields/url/component/url.ts b/src/addon/mod/data/fields/url/component/url.ts
new file mode 100644
index 000000000..f6d2a8450
--- /dev/null
+++ b/src/addon/mod/data/fields/url/component/url.ts
@@ -0,0 +1,46 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Component } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
+
+/**
+ * Component to render data url field.
+ */
+@Component({
+ selector: 'addon-mod-data-field-url',
+ templateUrl: 'url.html'
+})
+export class AddonModDataFieldUrlComponent extends AddonModDataFieldPluginComponent {
+
+ constructor(protected fb: FormBuilder) {
+ super(fb);
+ }
+
+ /**
+ * Initialize field.
+ */
+ protected init(): void {
+ if (this.isShowOrListMode()) {
+ return;
+ }
+
+ let value;
+ if (this.mode == 'edit' && this.value) {
+ value = this.value.content;
+ }
+
+ this.addControl('f_' + this.field.id, value);
+ }
+}
diff --git a/src/addon/mod/data/fields/url/providers/handler.ts b/src/addon/mod/data/fields/url/providers/handler.ts
new file mode 100644
index 000000000..7e6ea803e
--- /dev/null
+++ b/src/addon/mod/data/fields/url/providers/handler.ts
@@ -0,0 +1,57 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { Injector, Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { AddonModDataFieldTextHandler } from '../../text/providers/handler';
+import { AddonModDataFieldUrlComponent } from '../component/url';
+
+/**
+ * Handler for url data field plugin.
+ */
+@Injectable()
+export class AddonModDataFieldUrlHandler extends AddonModDataFieldTextHandler {
+ name = 'AddonModDataFieldUrlHandler';
+ type = 'url';
+
+ constructor(protected translate: TranslateService) {
+ super(translate);
+ }
+
+ /**
+ * Return the Component to use to display the plugin data.
+ * It's recommended to return the class of the component, but you can also return an instance of the component.
+ *
+ * @param {Injector} injector Injector.
+ * @param {any} field The field object.
+ * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, plugin: any): any | Promise {
+ return AddonModDataFieldUrlComponent;
+ }
+
+ /**
+ * Check and get field requeriments.
+ *
+ * @param {any} field Defines the field to be rendered.
+ * @param {any} inputData Data entered in the edit form.
+ * @return {string | false} String with the notification or false.
+ */
+ getFieldsNotifications(field: any, inputData: any): string | false {
+ if (field.required && (!inputData || !inputData.length || !inputData[0].value)) {
+ return this.translate.instant('addon.mod_data.errormustsupplyvalue');
+ }
+
+ return false;
+ }
+}
diff --git a/src/addon/mod/data/fields/url/url.module.ts b/src/addon/mod/data/fields/url/url.module.ts
new file mode 100644
index 000000000..09cd8911e
--- /dev/null
+++ b/src/addon/mod/data/fields/url/url.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonModDataFieldUrlHandler } from './providers/handler';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataFieldUrlComponent } from './component/url';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonModDataFieldUrlComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule
+ ],
+ providers: [
+ AddonModDataFieldUrlHandler
+ ],
+ exports: [
+ AddonModDataFieldUrlComponent
+ ],
+ entryComponents: [
+ AddonModDataFieldUrlComponent
+ ]
+})
+export class AddonModDataFieldUrlModule {
+ constructor(fieldDelegate: AddonModDataFieldsDelegate, handler: AddonModDataFieldUrlHandler) {
+ fieldDelegate.registerHandler(handler);
+ }
+}
diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json
new file mode 100644
index 000000000..fdd3f402a
--- /dev/null
+++ b/src/addon/mod/data/lang/en.json
@@ -0,0 +1,41 @@
+{
+ "addentries": "Add entries",
+ "advancedsearch": "Advanced search",
+ "alttext": "Alternative text",
+ "approve": "Approve",
+ "approved": "Approved",
+ "ascending": "Ascending",
+ "authorfirstname": "Author first name",
+ "authorlastname": "Author surname",
+ "confirmdeleterecord": "Are you sure you want to delete this entry?",
+ "descending": "Descending",
+ "disapprove": "Undo approval",
+ "emptyaddform": "You did not fill out any fields!",
+ "entrieslefttoadd": "You must add {{$a.entriesleft}} more entry/entries in order to complete this activity",
+ "entrieslefttoaddtoview": "You must add {{$a.entrieslefttoview}} more entry/entries before you can view other participants' entries.",
+ "errorapproving": "Error approving or unapproving entry.",
+ "errordeleting": "Error deleting entry.",
+ "errormustsupplyvalue": "You must supply a value here.",
+ "expired": "Sorry, this activity closed on {{$a}} and is no longer available",
+ "fields": "Fields",
+ "latlongboth": "Both latitude and longitude are required.",
+ "menuchoose": "Choose...",
+ "more": "More",
+ "nomatch": "No matching entries found!",
+ "norecords": "No entries in database",
+ "notapproved": "Entry is not approved yet.",
+ "notopenyet": "Sorry, this activity is not available until {{$a}}",
+ "numrecords": "{{$a}} entries",
+ "other": "Other",
+ "recordapproved": "Entry approved",
+ "recorddeleted": "Entry deleted",
+ "recorddisapproved": "Entry unapproved",
+ "resetsettings": "Reset filters",
+ "search": "Search",
+ "single": "View single",
+ "selectedrequired": "All selected required",
+ "single": "View single",
+ "timeadded": "Time added",
+ "timemodified": "Time modified",
+ "usedate": "Include in search."
+}
\ No newline at end of file
diff --git a/src/addon/mod/data/pages/edit/edit.html b/src/addon/mod/data/pages/edit/edit.html
new file mode 100644
index 000000000..85b2fbcba
--- /dev/null
+++ b/src/addon/mod/data/pages/edit/edit.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.groupsseparate' | translate }}
+ {{ 'core.groupsvisible' | translate }}
+
+ {{groupOpt.name}}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/mod/data/pages/edit/edit.module.ts b/src/addon/mod/data/pages/edit/edit.module.ts
new file mode 100644
index 000000000..fac3251a6
--- /dev/null
+++ b/src/addon/mod/data/pages/edit/edit.module.ts
@@ -0,0 +1,39 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreCommentsComponentsModule } from '@core/comments/components/components.module';
+import { CoreCompileHtmlComponentModule } from '@core/compile/components/compile-html/compile-html.module';
+import { AddonModDataComponentsModule } from '../../components/components.module';
+import { AddonModDataEditPage } from './edit';
+
+@NgModule({
+ declarations: [
+ AddonModDataEditPage,
+ ],
+ imports: [
+ CoreDirectivesModule,
+ CoreComponentsModule,
+ AddonModDataComponentsModule,
+ CoreCompileHtmlComponentModule,
+ CoreCommentsComponentsModule,
+ IonicPageModule.forChild(AddonModDataEditPage),
+ TranslateModule.forChild()
+ ],
+})
+export class AddonModDataEditPageModule {}
diff --git a/src/addon/mod/data/pages/edit/edit.ts b/src/addon/mod/data/pages/edit/edit.ts
new file mode 100644
index 000000000..55830538f
--- /dev/null
+++ b/src/addon/mod/data/pages/edit/edit.ts
@@ -0,0 +1,363 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, ViewChild } from '@angular/core';
+import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
+import { TranslateService } from '@ngx-translate/core';
+import { FormGroup } from '@angular/forms';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreGroupsProvider } from '@providers/groups';
+import { CoreEventsProvider } from '@providers/events';
+import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
+import { CoreCourseProvider } from '@core/course/providers/course';
+import { AddonModDataProvider } from '../../providers/data';
+import { AddonModDataHelperProvider } from '../../providers/helper';
+import { AddonModDataOfflineProvider } from '../../providers/offline';
+import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
+import { AddonModDataComponentsModule } from '../../components/components.module';
+
+/**
+ * Page that displays the view edit page.
+ */
+@IonicPage({ segment: 'addon-mod-data-edit' })
+@Component({
+ selector: 'page-addon-mod-data-edit',
+ templateUrl: 'edit.html',
+})
+export class AddonModDataEditPage {
+ @ViewChild(Content) content: Content;
+
+ protected module: any;
+ protected courseId: number;
+ protected data: any;
+ protected entryId: number;
+ protected entry: any;
+ protected offlineActions = [];
+ protected fields = {};
+ protected fieldsArray = [];
+ protected siteId: string;
+ protected offline: boolean;
+ protected forceLeave = false; // To allow leaving the page without checking for changes.
+
+ title = '';
+ component = AddonModDataProvider.COMPONENT;
+ loaded = false;
+ selectedGroup = 0;
+ cssClass = '';
+ cssTemplate = '';
+ groupInfo: any;
+ editFormRender = '';
+ editForm: FormGroup;
+ extraImports = [AddonModDataComponentsModule];
+ jsData: any;
+ errors = {};
+
+ constructor(params: NavParams, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider,
+ protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
+ protected courseProvider: CoreCourseProvider, protected dataProvider: AddonModDataProvider,
+ protected dataOffline: AddonModDataOfflineProvider, protected dataHelper: AddonModDataHelperProvider,
+ sitesProvider: CoreSitesProvider, protected navCtrl: NavController, protected translate: TranslateService,
+ protected eventsProvider: CoreEventsProvider, protected fileUploaderProvider: CoreFileUploaderProvider) {
+ this.module = params.get('module') || {};
+ this.entryId = params.get('entryId') || null;
+ this.courseId = params.get('courseId');
+ this.selectedGroup = params.get('group') || 0;
+
+ this.siteId = sitesProvider.getCurrentSiteId();
+
+ this.title = this.module.name;
+
+ this.editForm = new FormGroup({});
+ }
+
+ /**
+ * View loaded.
+ */
+ ionViewDidLoad(): void {
+ this.fetchEntryData();
+ }
+
+ /**
+ * Check if we can leave the page or not and ask to confirm the lost of data.
+ *
+ * @return {boolean | Promise} Resolved if we can leave it, rejected if not.
+ */
+ ionViewCanLeave(): boolean | Promise {
+ if (this.forceLeave) {
+ return true;
+ }
+
+ const inputData = this.editForm.value;
+
+ return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id,
+ this.entry.contents).then((changed) => {
+ if (!changed) {
+ return Promise.resolve();
+ }
+
+ // Show confirmation if some data has been modified.
+ return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
+ }).then(() => {
+ // Delete the local files from the tmp folder.
+ return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id,
+ this.entry.contents).then((files) => {
+ this.fileUploaderProvider.clearTmpFiles(files);
+ });
+ });
+ }
+
+ /**
+ * Fetch the entry data.
+ *
+ * @return {Promise} Resolved when done.
+ */
+ protected fetchEntryData(): Promise {
+ return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => {
+ this.title = data.name || this.title;
+ this.data = data;
+ this.cssClass = 'addon-data-entries-' + data.id;
+
+ return this.dataProvider.getDatabaseAccessInformation(data.id);
+ }).then((accessData) => {
+ this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.' + this.cssClass);
+
+ if (this.entryId) {
+ return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule, accessData.canmanageentries)
+ .then((groupInfo) => {
+ this.groupInfo = groupInfo;
+
+ // Check selected group is accessible.
+ if (groupInfo && groupInfo.groups && groupInfo.groups.length > 0) {
+ if (!groupInfo.groups.some((group) => this.selectedGroup == group.id)) {
+ this.selectedGroup = groupInfo.groups[0].id;
+ }
+ }
+ });
+ }
+ }).then(() => {
+ return this.dataOffline.getEntryActions(this.data.id, this.entryId);
+ }).then((actions) => {
+ this.offlineActions = actions;
+
+ return this.dataProvider.getFields(this.data.id);
+ }).then((fieldsData) => {
+ this.fieldsArray = fieldsData;
+ this.fields = this.utils.arrayToObject(fieldsData, 'id');
+
+ return this.dataHelper.getEntry(this.data, this.entryId, this.offlineActions);
+ }).then((entry) => {
+ if (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();
+ }).catch((message) => {
+ this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
+ }).finally(() => {
+ this.loaded = true;
+ });
+ }
+
+ /**
+ * Saves data.
+ *
+ * @return {Promise} Resolved when done.
+ */
+ save(): Promise {
+ const inputData = this.editForm.value;
+
+ return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id,
+ this.entry.contents).then((changed) => {
+
+ if (!changed) {
+ if (this.entryId) {
+ return this.returnToEntryList();
+ }
+
+ // New entry, no changes means no field filled, warn the user.
+ return Promise.reject('addon.mod_data.emptyaddform');
+ }
+
+ const modal = this.domUtils.showModalLoading('core.sending', true);
+
+ // Create an ID to assign files.
+ const entryTemp = this.entryId ? this.entryId : - (new Date().getTime());
+
+ return this.dataHelper.getEditDataFromForm(inputData, this.fieldsArray, this.data.id, entryTemp, this.entry.contents,
+ this.offline).catch((e) => {
+ if (!this.offline) {
+ // Cannot submit in online, prepare for offline usage.
+ this.offline = true;
+
+ return this.dataHelper.getEditDataFromForm(inputData, this.fieldsArray, this.data.id, entryTemp,
+ this.entry.contents, this.offline);
+ }
+
+ return Promise.reject(e);
+ }).then((editData) => {
+ if (editData.length > 0) {
+ if (this.entryId) {
+ return this.dataProvider.editEntry(this.data.id, this.entryId, this.courseId, editData, this.fields,
+ undefined, this.offline);
+ }
+
+ return this.dataProvider.addEntry(this.data.id, entryTemp, this.courseId, editData, this.selectedGroup,
+ this.fields, undefined, this.offline);
+ }
+
+ return false;
+ }).then((result: any) => {
+ if (!result) {
+ // No field filled, warn the user.
+ return Promise.reject('addon.mod_data.emptyaddform');
+ }
+
+ // This is done if entry is updated when editing or creating if not.
+ if ((this.entryId && result.updated) || (!this.entryId && result.newentryid)) {
+ const promises = [];
+
+ this.entryId = this.entryId || result.newentryid;
+
+ promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId, this.siteId));
+ promises.push(this.dataProvider.invalidateEntriesData(this.data.id, this.siteId));
+
+ return Promise.all(promises).then(() => {
+ this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED,
+ { dataId: this.data.id, entryId: this.entryId } , this.siteId);
+ }).finally(() => {
+ return this.returnToEntryList();
+ });
+ } else {
+ this.errors = {};
+ result.fieldnotifications.forEach((fieldNotif) => {
+ const field = this.fieldsArray.find((field) => field.name == fieldNotif.fieldname);
+ if (field) {
+ this.errors[field.id] = fieldNotif.notification;
+ }
+ });
+ this.jsData['errors'] = this.errors;
+
+ setTimeout(() => {
+ this.scrollToFirstError();
+ });
+ }
+ }).finally(() => {
+ modal.dismiss();
+ });
+ }).catch((error) => {
+ this.domUtils.showErrorModalDefault(error, 'Cannot edit entry', true);
+
+ return Promise.reject(null);
+ });
+
+ }
+
+ /**
+ * Set group to see the database.
+ *
+ * @param {number} groupId Group identifier to set.
+ * @return {Promise} Resolved when done.
+ */
+ setGroup(groupId: number): Promise {
+ this.selectedGroup = groupId;
+ this.loaded = false;
+
+ return this.fetchEntryData();
+ }
+
+ /**
+ * Displays Edit Search Fields.
+ *
+ * @return {string} Generated HTML.
+ */
+ protected displayEditFields(): string {
+ if (!this.data.addtemplate) {
+ return '';
+ }
+
+ this.jsData = {
+ fields: this.fields,
+ contents: this.entry.contents,
+ form: this.editForm,
+ data: this.data,
+ errors: this.errors
+ };
+
+ let replace,
+ render,
+ template = this.data.addtemplate;
+
+ // Replace the fields found on template.
+ this.fieldsArray.forEach((field) => {
+ replace = '[[' + field.name + ']]';
+ replace = replace.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
+ replace = new RegExp(replace, 'gi');
+
+ // Replace field by a generic directive.
+ render = '\
+ ';
+ template = template.replace(replace, render);
+
+ // Replace the field id tag.
+ replace = '[[' + field.name + '#id]]';
+ replace = replace.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
+ replace = new RegExp(replace, 'gi');
+
+ template = template.replace(replace, 'field_' + field.id);
+ });
+
+ return template;
+ }
+
+ /**
+ * Return to the entry list (previous page) discarding temp data.
+ *
+ * @return {Promise} Resolved when done.
+ */
+ protected returnToEntryList(): Promise {
+ const inputData = this.editForm.value;
+
+ return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id,
+ this.entry.contents).then((files) => {
+ this.fileUploaderProvider.clearTmpFiles(files);
+ }).finally(() => {
+ // Go back to entry list.
+ this.forceLeave = true;
+ this.navCtrl.pop();
+ });
+ }
+
+ /**
+ * Scroll to first error or to the top if not found.
+ */
+ protected scrollToFirstError(): void {
+ if (!this.domUtils.scrollToElementBySelector(this.content, '.addon-data-error')) {
+ this.content.scrollToTop();
+ }
+ }
+}
diff --git a/src/addon/mod/data/pages/entry/entry.html b/src/addon/mod/data/pages/entry/entry.html
new file mode 100644
index 000000000..55db45b17
--- /dev/null
+++ b/src/addon/mod/data/pages/entry/entry.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+