Views and layouts
Overview
With Hybridly, a view in your application consists of a regular single-file component.
Serving a view consists of returning a hybrid response with the component name and optional properties.
Views
Views are basic single-file components that can receive data from controllers as their properties. You can access these properties like any other single-file component using defineProps
.
<script setup lang="ts">
const $props = defineProps<{
user: App.Data.User
}>()
useHead({
title: () => `${$props.user.full_name}'s profile`
})
</script>
<template>
<section>
<h1 v-text="user.full_name" />
<!-- ... -->
</section>
</template>
The route and controller for the view above could look like the following:
Route::get('/users/{user}', ShowUserController::class)->name('users.show');
use App\Data\UserData;
use App\Models\User;
class ShowUserController
{
public function __invoke(User $user)
{
return hybridly('users.show', [
'user' => UserData::from($user)
]);
}
}
Layouts
A view layout can be a basic single-file component as well. You can simply wrap your view's content in the layout component, like you would do in a basic Vue project.
<script setup lang="ts">
import MainLayout from '@/views/layouts/main.vue'
</script>
<template>
<main-layout>
<!-- ... -->
</main-layout>
</template>
However, this technique has a drawback: when navigating between views, the layout will be destroyed and re-mounted. This means you cannot have persistent state in it.
Persistent layouts
Persistent layouts are components that will not get destroyed upon navigation. Their state will be persisted when changing views.
A persistent layout can be defined in two ways. You can use the layout
attribute on the template
element of the view component:
<template layout="main">
<!-- ... -->
</template>
In the example above, the resources/views/layouts/main.vue
layout will be used. If the name of the layout is omitted, layouts/default.vue
will be used instead. So if you only have one layout, you can use the following:
<template layout>
<!-- ... -->
</template>
The other way to define a persistent layout is to use the defineOptions
function:
<script setup lang="ts">
import main from '@/views/layouts/main.vue'
defineOptions({
layout: main
})
</script>
<template>
<!-- ... -->
</template>
You can also pass layout properties to the properties
object:
defineOptions({
layout: main,
properties: {
fullscreen: false,
}
})
Named slots
Persistent layouts also have their own drawbacks. Specifically, it is not possible to use named slots with them. Instead, use properties or basic single-file component layouts.
Persistent layout properties
Additional properties can be passed to persistent layouts on a per-view basis. When navigating away from a view, the properties will be reset.
You may use the defineOptions
macro to define the layout properties:
<!-- resources/views/views/example.vue -->
<script setup lang="ts">
defineOptions({
properties: {
fluid: true,
}
})
</script>
<template layout="main">
<!-- ... -->
</template>
<!-- resources/views/layouts/main.vue -->
<script setup lang="ts">
defineProps<{
fluid?: boolean
}>()
</script>
<template>
<main :class="{
'container mx-auto': fluid,
'w-full mx-10': !fluid,
}">
<slot />
</main>
</template>
Note that properties are not made reactive by default. Similar to Vue's inject
/provide
, you will need to make them reactive yourself.