Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions locales/default.pot
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ msgstr ""
msgid "Forgot your password?"
msgstr ""

msgid "Form data has been modified. Save it to persist the changes"
msgstr ""

msgid "Format choosen is not available"
msgstr ""

Expand Down
3 changes: 3 additions & 0 deletions locales/en_US/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ msgstr ""
msgid "Forgot your password?"
msgstr ""

msgid "Form data has been modified. Save it to persist the changes"
msgstr ""

msgid "Format choosen is not available"
msgstr ""

Expand Down
3 changes: 3 additions & 0 deletions locales/it_IT/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ msgstr "Non consentito"
msgid "Forgot your password?"
msgstr "Hai dimenticato la password?"

msgid "Form data has been modified. Save it to persist the changes"
msgstr "Dati del form modificati. Salva per mantenere le modifiche"

msgid "Format choosen is not available"
msgstr "Il formato selezionato non è disponibile"

Expand Down
113 changes: 110 additions & 3 deletions resources/js/app/components/relation-view/relation-view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export default {
return {
method: 'related', // define AppController method to be used
loading: false,
resettingRelated: false,
savingRelated: false,
objectsLoaded: false, // objects loaded flag
positions: {}, // used in children relations
priorities: {}, // used in children relations
Expand All @@ -97,6 +99,9 @@ export default {
activeFilter: {}, // current active filter for objects list

exportFormat: 'csv', // default csv

originalData: '', // original data for comparison
changedOriginalData: false,
}
},

Expand Down Expand Up @@ -210,6 +215,8 @@ export default {
}
});

this.updateOriginalData();

// enable related objects drop
this.$on('sort-end', this.onSort);
},
Expand Down Expand Up @@ -311,6 +318,7 @@ export default {

priority = this.objects[i].meta.relation.priority;
}
this.updateOriginalData();
},

/**
Expand Down Expand Up @@ -362,6 +370,7 @@ export default {

return object;
});
this.updateOriginalData();
},

/**
Expand Down Expand Up @@ -430,6 +439,7 @@ export default {
}
}
this.prepareRelationsToSave();
this.updateOriginalData();
},

/**
Expand Down Expand Up @@ -520,28 +530,62 @@ export default {
*
* @return {Array} objs objects retrieved
*/
async loadRelatedObjects(filter = {}, force = false) {
async loadRelatedObjects(filter = {}, force = false, reset = false) {
if (this.preCount === 0 || (this.objectsLoaded && !force)) {
if (reset) {
this.addedRelations = [];
this.modifiedRelations = [];
this.removedRelated = [];
}
return [];
}
this.loading = true;
if (reset) {
this.resettingRelated = true;
}

return this.getPaginatedObjects(true, filter)
.then((objs) => {
this.$emit('count', this.pagination.count);
this.loading = false;
this.objectsLoaded = true;
if (reset) {
this.addedRelations = [];
this.modifiedRelations = [];
this.removedRelated = [];
}
this.updateOriginalData();

return objs;
})
.catch((error) => {
// code 20 is user aborted fetch which is ok
if (error.code !== 20) {
this.loading = false;
console.error(error);
}
})
.finally(() => {
this.loading = false;
if (reset) {
this.resettingRelated = false;
}
});
},

updateOriginalData() {
const data = this.objects.map(o => o.id);
if (!this.originalData) {
this.originalData = {};
}
const originalData = JSON.stringify(data);
const p = {...this.pagination};
const k = `${this.relationName}-count_${p.count}-pagecount_${p.page_count}-pageitems_${p.page_items || 0}-pagesize_${p.page_size}`;
this.originalData[k] = originalData;
const changedIds = originalData !== JSON.stringify(this.alreadyInView || []);
const changedAny = (this.addedRelations.length + this.modifiedRelations.length + this.removedRelated.length) > 0;
this.changedOriginalData = changedAny || changedIds;
},

/**
* Reset the filter and reload all related objects.
*
Expand All @@ -552,6 +596,11 @@ export default {
return this.loadRelatedObjects({}, true);
},

resetObjects() {
this.activeFilter = {};
return this.loadRelatedObjects({}, true, true);
},

/**
* toggle relation
*
Expand Down Expand Up @@ -588,6 +637,7 @@ export default {
// if yes we unstage it
this.prepareRelationsToSave();
}
this.updateOriginalData();
},

/**
Expand All @@ -607,6 +657,7 @@ export default {
// if yes we stage it for saving
this.prepareRelationsToSave();
}
this.updateOriginalData();
},

/**
Expand All @@ -629,6 +680,59 @@ export default {
}));
},

async saveRelated(data) {
try {
this.savingRelated = true;
const relationName = data.relationName;
const objectType = data.object.type;
const url = `${BEDITA.base}/api/${objectType}/${data.object.id}/relationships/${relationName}`;
if (this.removedRelationsData.length > 0) {
// save removed relations
const response = await fetch(url, {
method: 'DELETE',
credentials: 'same-origin',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'X-CSRF-Token': BEDITA.csrfToken,
},
body: JSON.stringify({ data: JSON.parse(this.removedRelationsData) }),
});
if (response?.error) {
BEDITA.error(response.error);
} else {
this.removedRelated = [];
this.prepareRelationsToRemove(this.removedRelated);
}
}
if (this.addedRelationsData.length > 0) {
// save added relations
const response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'X-CSRF-Token': BEDITA.csrfToken,
},
body: JSON.stringify({ data: JSON.parse(this.addedRelationsData) }),
});
if (response.error) {
BEDITA.error(response.error);
} else {
this.addedRelations = [];
this.modifiedRelations = [];
this.prepareRelationsToSave();
}
}
await this.reloadObjects();
} catch (error) {
BEDITA.error(error);
} finally {
this.savingRelated = false;
}
},

/**
* prepare removeRelated Array for saving using serialized json input field
*
Expand Down Expand Up @@ -658,7 +762,7 @@ export default {
}
this.addedRelations = this.addedRelations.filter((rel) => rel.id !== id);
PanelEvents.send('relations-view:remove-already-in-view', null, { id } );

this.updateOriginalData();

this.prepareRelationsToSave();
},
Expand All @@ -684,6 +788,7 @@ export default {
this.modifiedRelations.push(related);
}
this.prepareRelationsToSave();
this.updateOriginalData();
},

/**
Expand All @@ -700,6 +805,7 @@ export default {
}
this.modifiedRelations = this.modifiedRelations.filter((rel) => rel.id !== id);
this.prepareRelationsToSave();
this.updateOriginalData();
},

/**
Expand Down Expand Up @@ -776,6 +882,7 @@ export default {
});
// sort by restored priorities
this.objects.sort((a, b) => a.meta.relation.priority - b.meta.relation.priority);
this.updateOriginalData();
})
.catch((error) => {
// code 20 is user aborted fetch which is ok
Expand Down
55 changes: 45 additions & 10 deletions templates/Element/Form/relation.twig
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
{% set dragAndDrop = relationName != 'children' or (relationName == 'children' and mainObject.attributes.children_order in [null, 'position', '-position']) %}
{# RELATED OBJECTS #}
<div class="ml-05 mb-1" v-if="loading">{{ __('Loading ...') }}</div>
<div class="ml-05 mb-1" v-if="!loading && objects.length === 0">{{ __('No items found') }}</div>
<div class="ml-05 mb-1" v-if="!loading && objects.length === 0" v-if="addedRelations.length == 0">{{ __('No items found') }}</div>
<div class="related-objects mb-1" v-show="objects.length || addedRelations.length">
<div :data-list="dataList" class="columns" {%- if dragAndDrop %}droppable sortable{%- endif %}>
<div
Expand Down Expand Up @@ -118,10 +118,15 @@
{% endif %}
</div>
</div>

</div>

{% if Perms.canSave() and not readonly %}
<div v-if="changedOriginalData">
<p class="is-expanded tag mt-1">
<app-icon icon="carbon:filter"></app-icon>
<span class="ml-05">{{ __('Form data has been modified. Save it to persist the changes') }}</span>
</p>
</div>
<div class="mt-5">
{# DROP FILES #}
{% if uploadableNum %}
Expand All @@ -139,17 +144,47 @@
<span class="ml-05">{{ __('cancel') }}</span>
</button>

<button v-else
@click.prevent.stop="addRelatedObjects({
object: {{object|json_encode}},
relationName: relationName,
relationLabel: relationLabel,
alreadyInView: alreadyInView,
relationTypes: relationTypes,
})">
<template v-else>
<button
class="button button-primary button-primary-hover-module-{{ currentModule.name }} is-width-auto"
:class="{'is-loading-spinner': resettingRelated}"
:disabled="!changedOriginalData"
@click.prevent.stop="resetObjects"
v-if="relationName != 'children'"
>
<app-icon icon="carbon:filter-reset"></app-icon>
<span class="ml-05">{{ __('Reset') }}</span>
</button>
<button
class="button button-primary button-primary-hover-module-{{ currentModule.name }} is-width-auto"
:class="{'is-loading-spinner': savingRelated}"
:disabled="!changedOriginalData"
@click.prevent.stop="saveRelated({
object: {{object|json_encode}},
relationName: relationName,
relationLabel: relationLabel,
alreadyInView: alreadyInView,
relationTypes: relationTypes,
})"
v-if="relationName != 'children'"
>
<app-icon icon="carbon:save"></app-icon>
<span class="ml-05">{{ __('Save') }}</span>
</button>

<button
class="button button-primary button-primary-hover-module-{{ currentModule.name }} is-width-auto"
@click.prevent.stop="addRelatedObjects({
object: {{object|json_encode}},
relationName: relationName,
relationLabel: relationLabel,
alreadyInView: alreadyInView,
relationTypes: relationTypes,
})">
<app-icon icon="carbon:add"></app-icon>
<span class="ml-05">{{ __('add objects') }}</span>
</button>
</template>
</div>
</div>
{% endif %} {# Perms #}
Expand Down
Loading