Interface one to many (#533)

* Extract edit modal to standalone component

* Fix creating new item from m2o edit modal

* Rename item-modal to modal-detail

* Extract selection modal in standalone component

* Add required primary-key prop to v-form

* Add inline prop to table

* Fetch items in o2m

* Accept numbers for primary key in v-form

* Use correct collection in render template in m2o

* Render modal detail

* Fix edit existing

* Add add-new

* Do things

* Finish o2m
This commit is contained in:
Rijk van Zanten
2020-05-07 10:53:51 -04:00
committed by GitHub
parent b4fdf96900
commit 0c17735e0e
25 changed files with 1003 additions and 212 deletions

View File

@@ -0,0 +1,4 @@
import ModalBrowse from './modal-browse.vue';
export { ModalBrowse };
export default ModalBrowse;

View File

@@ -0,0 +1,121 @@
<template>
<v-modal v-model="_active" :title="$t('select_item')" no-padding>
<layout-tabular
class="layout"
:collection="collection"
:selection="_selection"
:filters="filters"
@update:selection="onSelect"
select-mode
/>
<template #footer>
<v-button @click="cancel" secondary>{{ $t('cancel') }}</v-button>
<v-button @click="save">{{ $t('save') }}</v-button>
</template>
</v-modal>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, computed } from '@vue/composition-api';
import { Filter } from '@/stores/collection-presets/types';
export default defineComponent({
props: {
active: {
type: Boolean,
default: false,
},
selection: {
type: Array as PropType<(number | string)[]>,
default: () => [],
},
collection: {
type: String,
required: true,
},
multiple: {
type: Boolean,
default: false,
},
filters: {
type: Array as PropType<Filter[]>,
default: () => [],
},
},
setup(props, { emit }) {
const { save, cancel } = useActions();
const { _active } = useActiveState();
const { _selection, onSelect } = useSelection();
return { save, cancel, _active, _selection, onSelect };
function useActiveState() {
const localActive = ref(false);
const _active = computed({
get() {
return props.active === undefined ? localActive.value : props.active;
},
set(newActive: boolean) {
localActive.value = newActive;
emit('update:active', newActive);
},
});
return { _active };
}
function useSelection() {
const localSelection = ref<(string | number)[]>(null);
const _selection = computed({
get() {
if (localSelection.value === null) {
return props.selection;
}
return localSelection.value;
},
set(newSelection: (string | number)[]) {
localSelection.value = newSelection;
},
});
return { _selection, onSelect };
function onSelect(newSelection: (string | number)[]) {
if (newSelection.length === 0) {
localSelection.value = [];
return;
}
if (props.multiple === true) {
localSelection.value = newSelection;
} else {
localSelection.value = [newSelection[newSelection.length - 1]];
}
}
}
function useActions() {
return { save, cancel };
function save() {
emit('input', _selection.value);
_active.value = false;
}
function cancel() {
_active.value = false;
}
}
},
});
</script>
<style lang="scss" scoped>
.layout {
--layout-offset-top: 0px;
}
</style>

View File

@@ -0,0 +1,4 @@
import ModalDetail from './modal-detail.vue';
export { ModalDetail };
export default ModalDetail;

View File

@@ -0,0 +1,144 @@
<template>
<v-modal v-model="_active" :title="$t('editing_in', { collection })" persistent>
<v-form
:loading="loading"
:initial-values="item"
:collection="collection"
:primary-key="primaryKey"
v-model="_edits"
/>
<template #footer>
<v-button @click="cancel" secondary>{{ $t('cancel') }}</v-button>
<v-button @click="save">{{ $t('save') }}</v-button>
</template>
</v-modal>
</template>
<script lang="ts">
import { defineComponent, ref, computed, PropType, watch } from '@vue/composition-api';
import api from '@/api';
import useProjectsStore from '@/stores/projects';
export default defineComponent({
model: {
prop: 'edits',
},
props: {
active: {
type: Boolean,
default: false,
},
collection: {
type: String,
required: true,
},
primaryKey: {
type: [String, Number],
required: true,
},
edits: {
type: Object as PropType<Record<string, any>>,
default: undefined,
},
},
setup(props, { emit }) {
const projectsStore = useProjectsStore();
const { _active } = useActiveState();
const { _edits, loading, error, item } = useItem();
const { save, cancel } = useActions();
return { _active, _edits, loading, error, item, save, cancel };
function useActiveState() {
const localActive = ref(false);
const _active = computed({
get() {
return props.active === undefined ? localActive.value : props.active;
},
set(newActive: boolean) {
localActive.value = newActive;
emit('update:active', newActive);
},
});
return { _active };
}
function useItem() {
const localEdits = ref<Record<string, any>>({});
const _edits = computed<Record<string, any>>({
get() {
if (props.edits !== undefined) {
return {
...props.edits,
...localEdits.value,
};
}
return localEdits.value;
},
set(newEdits) {
localEdits.value = newEdits;
},
});
const loading = ref(false);
const error = ref(null);
const item = ref<Record<string, any>>(null);
watch(
() => props.active,
(isActive) => {
if (isActive === true) {
if (props.primaryKey !== '+') fetchItem();
} else {
loading.value = false;
error.value = null;
item.value = null;
localEdits.value = {};
}
}
);
return { _edits, loading, error, item, fetchItem };
async function fetchItem() {
const { currentProjectKey } = projectsStore.state;
loading.value = true;
try {
const response = await api.get(
`/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`
);
item.value = response.data.data;
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
}
function useActions() {
return { save, cancel };
function save() {
emit('input', _edits.value);
_active.value = false;
_edits.value = {};
}
function cancel() {
_active.value = false;
_edits.value = {};
}
}
},
});
</script>

View File

@@ -1,6 +1,6 @@
<template>
<value-null v-if="value === null || value === undefined" />
<span v-else-if="!displayInfo">{{ value }}</span>
<span v-else-if="displayInfo === null">{{ value }}</span>
<span v-else-if="typeof displayInfo.handler === 'function'">
{{ display.handler(value, options) }}
</span>
@@ -15,7 +15,7 @@
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { defineComponent, computed } from '@vue/composition-api';
import displays from '@/displays';
import ValueNull from '@/views/private/components/value-null';
@@ -44,7 +44,9 @@ export default defineComponent({
},
},
setup(props) {
const displayInfo = displays.find((display) => display.id === props.display) || null;
const displayInfo = computed(
() => displays.find((display) => display.id === props.display) || null
);
return { displayInfo };
},
});