Appearance
Relation Widget Component
The Relation Widget component provides an interface for managing related entities on the edit view. In this example, the widget is used to display and manage the Steps related to a Recipe. It allows users to view associated steps, dissociate existing ones, and open dialogs to either connect additional steps or create new ones.
Let's examine an example of a generated Relation Widget component for managing Steps:
vue
<template>
<Card class="steps-relation-widget">
<template #title>
<h4 class="steps-relation-widget__title">Steps</h4>
<Button
icon="fal fa-link"
label="Connect"
outlined
severity="secondary"
@click="isRelationAddDialogActive = true" />
<Button
icon="fal fa-plus"
label="Create"
outlined
severity="secondary"
@click="isFormActive = true" />
</template>
<template #content>
<ApiTable
flat
:list-state="listState"
@row-click="openDetails">
<Column
field="instruction"
header="Instruction" />
<Column
header=""
:style="{ maxWidth: '72px', width: '72px' }">
<template #body="slotProps">
<Button
severity="secondary"
outlined
rounded
icon="fal fa-times"
:loading="dissociateLoading === slotProps.data.id"
@click.stop.prevent="dissociate(slotProps.data)" />
</template>
</Column>
</ApiTable>
</template>
</Card>
<StepsRelationAddDialog
v-model="isRelationAddDialogActive"
:recipe-id="props.recipeId"
@update="refresh()" />
<StepForm
:as-dialog="true"
:visible="isFormActive"
:should-redirect="false"
:force-values="{ recipe_id: Number(props.recipeId) }"
:hide-inputs="['recipe_id']"
@close="isFormActive = false"
@created="refresh()" />
</template>
<script setup lang="ts">
import { defineProps, onBeforeMount, ref } from 'vue'
import StepsRelationAddDialog from './StepsRelationAddDialog.vue'
import StepForm from '@/views/Step/components/Form.vue'
import Card from 'primevue/card'
import ApiTable from '@/components/Table/ApiTable.vue'
import Column from 'primevue/column'
import Button from 'primevue/button'
import { useRouter } from 'vue-router'
import { useStepListState } from '@/models/Step/States'
import type { Step } from '@/models/Step/Model'
import type { Recipe } from '@/models/Recipe/Model'
import RecipesApi from '@/models/Recipe/Api'
const emit = defineEmits(['start-loading', 'stop-loading'])
const props = defineProps<{
recipeId: Recipe['id']
}>()
const router = useRouter()
const isRelationAddDialogActive = ref(false)
const isFormActive = ref(false)
const dissociateLoading = ref(null as null | number | string)
const listLoading = ref(false)
const listState = useStepListState()
listState.defaultParams = {
per_page: 10,
fromRelation: {
model: 'App\\Models\\Recipe',
id: props.recipeId,
relation: 'steps',
},
}
onBeforeMount(() => {
refresh()
})
async function refresh() {
listLoading.value = true
emit('start-loading')
await listState.getList()
listLoading.value = false
emit('stop-loading')
}
async function dissociate(item: Step) {
dissociateLoading.value = item.id
emit('start-loading')
await new RecipesApi().updateRelation(props.recipeId, 'steps', {
method: 'dissociate',
params: [item.id],
})
dissociateLoading.value = null
await refresh()
emit('stop-loading')
}
function openDetails(item: { data: Step }) {
router.push({ name: 'steps-edit', params: { id: item.data.id } })
}
</script>
<style lang="scss" scoped>
.steps-relation-widget {
width: 100%;
max-width: 600px;
overflow: hidden;
:deep(.p-card-body) {
padding: 20px 0 0;
.p-card-caption {
padding: 0px 20px 12px;
.p-card-title {
display: flex;
align-items: center;
gap: 10px;
.steps-relation-widget__title {
flex: 1;
text-align: left;
}
}
}
}
}
</style>
Overview
The Relation Widget component offers a compact, card-based interface to manage related entities. In this example, it displays the Steps associated with a Recipe, providing a table view of the related items along with options to:
Connect: Open a dialog to add existing Steps via a multi-select table.
Create: Launch a form to create a new Step, which will automatically be assigned to the Recipe.
Dissociate: Remove an existing Step association directly from the table.
See also
Key Features
Dynamic Card Header: The header slot of the Card displays the title ("Steps") along with two action buttons:
- Connect Button: Opens the StepsRelationAddDialog.
- Create Button: Opens the StepForm dialog for creating a new Step.
Selectable Table: The table (
ApiTable
) within the content slot shows the current related Steps. Each row includes:- A column displaying the step's instruction.
- A dissociate button in each row that allows the removal of the association.
Dialog Integration:
- StepsRelationAddDialog: This dialog allows users to select and add multiple Steps.
- StepForm: This dialog is used for creating a new Step. It is configured to automatically set the
recipe_id
to the current Recipe.
See also
State Management & Behavior
List State: The widget uses a
listState
(fromuseStepListState
) to manage the table data. The default query parameters ensure that only Steps associated with the current Recipe are retrieved (via thefromRelation
parameter).Loading Indicators: Loading states for dissociating steps and fetching the list are managed with reactive variables (
dissociateLoading
,listLoading
). The component emits global loading events (start-loading
andstop-loading
) to coordinate with the parent view.Refreshing Data: The
refresh
function is called to update the list of Steps whenever a relation is modified. This function is triggered after dissociation and when dialogs emit an update event.Routing: Clicking on a table row navigates to the edit view for that specific Step using Vue Router.
See also
Summary
The generated Relation Widget component streamlines the management of related entities by integrating a dynamic card interface, a selectable table, and modal dialogs for connecting or creating new relations. It provides an intuitive user experience for viewing, adding, and removing relationships between entities.