Skip to content

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 (from useStepListState) to manage the table data. The default query parameters ensure that only Steps associated with the current Recipe are retrieved (via the fromRelation 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 and stop-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.