Skip to content

Commit

Permalink
Unified and abstracted DataTables as XDataTable component (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlhaufe authored Jun 11, 2024
1 parent 777c3bf commit 70842eb
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 1,071 deletions.
144 changes: 144 additions & 0 deletions components/XDataTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<script lang="ts" setup>
import { type Uuid, emptyUuid } from '~/domain/Uuid'
// export type RowType = { id: Uuid, name: string }
export type RowType = any
const props = defineProps<{
datasource: RowType[],
filters: Record<string, { value: any, matchMode: string }>,
emptyRecord: { id: Uuid, name: string },
onCreate: (data: RowType) => Promise<void>,
onDelete: (id: Uuid) => Promise<void>,
onUpdate: (data: RowType) => Promise<void>
}>()
const dataTable = ref<any>(),
createDisabled = ref(false),
editingRows = ref<RowType[]>([]),
sortField = ref<string | undefined>('name'),
confirm = useConfirm()
const onCreateEmpty = async () => {
props.datasource.unshift(Object.assign({}, props.emptyRecord))
editingRows.value = [props.datasource[0]]
createDisabled.value = true
// remove the sortfield to avoid the new row from being sorted
sortField.value = undefined
// focus on the first input
setTimeout(() => {
const input = dataTable.value!.$el.querySelector('.p-datatable-tbody tr input')! as HTMLInputElement
input.focus()
}, 100)
}
const onDelete = (item: RowType) => new Promise<void>((resolve, _reject) => {
confirm.require({
message: `Are you sure you want to delete ${item.name}?`,
header: 'Delete Confirmation',
icon: 'pi pi-exclamation-triangle',
rejectLabel: 'Cancel',
acceptLabel: 'Delete',
accept: async () => {
await props.onDelete(item.id)
resolve()
},
reject: () => { }
})
})
const onCancel = ({ data, index }: { data: RowType, index: number }) => {
if (data.id !== emptyUuid)
return
props.datasource.splice(index, 1)
createDisabled.value = false
sortField.value = 'name'
}
const onRowEditSave = async (event: { newData: RowType, originalEvent: Event }) => {
const { newData, originalEvent } = event
const row = (originalEvent.target! as HTMLElement).closest('tr')!,
inputs = row.querySelectorAll('input'),
dropDowns = row.querySelectorAll('.p-dropdown[required="true"]')
if (![...inputs].every(o => o.reportValidity())) {
editingRows.value = [newData]
return
}
if (![...dropDowns].every(dd => {
const value = dd.querySelector('.p-inputtext')!.textContent?.trim(),
result = value !== '' && !value?.startsWith('Select')
dd.classList.toggle('p-invalid', !result)
return result
})) {
editingRows.value = [newData]
return
}
if (newData.id === emptyUuid) {
await props.onCreate(newData)
createDisabled.value = false
} else {
await props.onUpdate(newData)
}
}
const onRowEditInit = ({ originalEvent }: any) => {
// focus on the first input when editing
const row = originalEvent.target.closest('tr')
setTimeout(() => {
const input = row.querySelector('input')
input.focus()
}, 100)
}
const onSort = (event: any) => {
if (editingRows.value.length > 0) {
// cancel editing of the dummy row
if (editingRows.value[0].id === emptyUuid)
onCancel({ data: editingRows.value[0], index: 0 })
editingRows.value = []
createDisabled.value = false
}
}
</script>

<template>
<ConfirmDialog></ConfirmDialog>
<Toolbar>
<template #start>
<Button label="Create" type="submit" severity="info" @click="onCreateEmpty" :disabled="createDisabled" />
</template>
</Toolbar>
<DataTable ref="dataTable" :value="props.datasource as unknown as any[]" dataKey="id" filterDisplay="row"
v-model:filters="filters as any" :globalFilterFields="Object.keys(filters)" editMode="row"
@row-edit-init="onRowEditInit" v-model:editingRows="editingRows" @row-edit-save="onRowEditSave"
@row-edit-cancel="onCancel" @sort="onSort" :sortField="sortField" :sortOrder="1">
<slot></slot>
<Column frozen align-frozen="right">
<template #body="{ data, editorInitCallback }">
<Button icon="pi pi-pencil" text rounded @click="editorInitCallback" />
<Button icon="pi pi-trash" text rounded severity="danger" @click="onDelete(data)" />
</template>
<template #editor="{ editorSaveCallback, editorCancelCallback }">
<Button icon="pi pi-check" text rounded @click="editorSaveCallback" />
<Button icon="pi pi-times" text rounded severity="danger" @click="editorCancelCallback" />
</template>
</Column>
<template #empty>No data found</template>
<template #loading>Loading data...</template>
</DataTable>
</template>

<style scoped>
:deep(.p-cell-editing) {
background-color: var(--highlight-bg);
}
</style>
107 changes: 20 additions & 87 deletions modules/environment/ui/pages/Assumptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UpdateAssumptionUseCase from '../../application/UpdateAssumptionUseCase';
import DeleteAssumptionUseCase from '../../application/DeleteAssumptionUseCase';
import type Assumption from '../../domain/Assumption';
import { FilterMatchMode } from 'primevue/api';
import { emptyUuid } from '~/domain/Uuid';
import { emptyUuid, type Uuid } from '~/domain/Uuid';
const router = useRouter(),
route = useRoute(),
Expand All @@ -37,78 +37,35 @@ if (!solution) {
type AssumptionViewModel = Pick<Assumption, 'id' | 'name' | 'statement'>;
const assumptions = ref<AssumptionViewModel[]>([]),
editingRows = ref<AssumptionViewModel[]>([]),
dataTable = ref();
emptyAssumption = { id: emptyUuid, name: '', statement: '' };
onMounted(async () => {
assumptions.value = await getAssumptionsUseCase.execute(environment!.id) ?? []
})
const filters = ref({
const filters = ref<Record<string, { value: any, matchMode: string }>>({
'name': { value: null, matchMode: FilterMatchMode.CONTAINS },
'statement': { value: null, matchMode: FilterMatchMode.CONTAINS },
});
const createDisabled = ref(false)
const onCreate = async (data: AssumptionViewModel) => {
const newId = await createAssumptionUseCase.execute({
parentId: environment!.id,
name: data.name,
statement: data.statement
})
const addNewRow = () => {
assumptions.value.unshift({ id: emptyUuid, name: '', statement: '' })
editingRows.value = [assumptions.value[0]]
createDisabled.value = true
// remove the sortfield to avoid the new row from being sorted
dataTable.value!.d_sortField = null
}
const onRowEditSave = async (event: { newData: AssumptionViewModel, index: number, originalEvent: Event }) => {
const { newData, index, originalEvent } = event
const inputs = (originalEvent.target! as HTMLElement).closest('tr')!.querySelectorAll('input')
if (![...inputs].every(o => o.reportValidity())) {
editingRows.value = [newData]
return
}
if (newData.id === emptyUuid) {
const newId = await createAssumptionUseCase.execute({
parentId: environment!.id,
name: newData.name,
statement: newData.statement
})
assumptions.value[index] = {
id: newId,
name: newData.name,
statement: newData.statement
}
createDisabled.value = false
} else {
assumptions.value[index] = newData
await updateAssumptionUseCase.execute(newData)
}
}
const onRowEditCancel = ({ data, index }: { data: AssumptionViewModel, index: number }) => {
if (data.id !== emptyUuid)
return
assumptions.value.splice(index, 1)
createDisabled.value = false
dataTable.value!.d_sortField = 'name'
assumptions.value = await getAssumptionsUseCase.execute(environment!.id) ?? []
}
const onRowDelete = async (assumption: AssumptionViewModel) => {
if (!confirm(`Are you sure you want to delete ${assumption.name}?`))
return
assumptions.value = assumptions.value.filter(o => o.id !== assumption.id)
await deleteAssumptionUseCase.execute(assumption.id)
const onDelete = async (id: Uuid) => {
await deleteAssumptionUseCase.execute(id)
assumptions.value = await getAssumptionsUseCase.execute(environment!.id) ?? []
}
const onSort = (event: any) => {
if (editingRows.value.length > 0) {
editingRows.value = []
createDisabled.value = false
}
const onUpdate = async (data: AssumptionViewModel) => {
await updateAssumptionUseCase.execute(data)
assumptions.value = await getAssumptionsUseCase.execute(environment!.id) ?? []
}
</script>

Expand All @@ -119,15 +76,8 @@ const onSort = (event: any) => {
An example of an assumption would be: "Screen resolution will not change during
the execution of the program".
</p>
<Toolbar>
<template #start>
<Button label="Create" type="submit" severity="info" @click="addNewRow" :disabled="createDisabled" />
</template>
</Toolbar>
<DataTable ref="dataTable" :value="assumptions" dataKey="id" filterDisplay="row" v-model:filters="filters"
:globalFilterFields="['name', 'statement']" editMode="row" v-model:editingRows="editingRows"
@row-edit-save="onRowEditSave" @row-edit-cancel="onRowEditCancel" @sort="onSort" sortField="name"
:sortOrder="1">
<XDataTable :datasource="assumptions" :filters="filters" :empty-record="emptyAssumption" :on-create="onCreate"
:on-delete="onDelete" :on-update="onUpdate">
<Column field="name" header="Name" sortable>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model.trim="filterModel.value" @input="filterCallback()" placeholder="Search by name" />
Expand All @@ -151,22 +101,5 @@ const onSort = (event: any) => {
<InputText v-model.trim="data[field]" required="true" />
</template>
</Column>
<Column frozen align-frozen="right">
<template #body="{ data, editorInitCallback }">
<Button icon="pi pi-pencil" text rounded @click="editorInitCallback" />
<Button icon="pi pi-trash" text rounded severity="danger" @click="onRowDelete(data)" />
</template>
<template #editor="{ editorSaveCallback, editorCancelCallback }">
<Button icon="pi pi-check" text rounded @click="editorSaveCallback" />
<Button icon="pi pi-times" text rounded severity="danger" @click="editorCancelCallback" />
</template>
</Column>
<template #empty>No assumptions found</template>
<template #loading>Loading assumptions...</template>
</DataTable>
</template>
<style scoped>
:deep(.p-cell-editing) {
background-color: var(--highlight-bg);
}
</style>
</XDataTable>
</template>
Loading

0 comments on commit 70842eb

Please sign in to comment.