Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unified and abstracted DataTables as XDataTable component #137

Merged
merged 1 commit into from
Jun 11, 2024
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
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
Loading