Get Your Front-end Rolling with Vue and InertiaJS
What to expect
- An overview of cbInertia and Inertia.js
- Why you would use this library
- Example app to explore performance
- Live coding some cbInertia views!
It will help if you are familiar with...
- ColdBox Handlers and Routing
- Vue Components and Templates
Who Am I?
Utah
Ortus Solutions
Prolific Module Author
1 wife, 3 kids, 1 dog
Type 1 Diabetic
Theatre Nerd
What is cbInertia?
Why am I learning about another JavaScript framework?
Today's Web Apps
- Fast
- Lightweight
- Inertactive
Option #1
Multi-page Server-rendered App
Multi-page Server-rendered App
Cons
- Full page refresh for each request
- JavaScript feels bolted on
Pros
- Codebase you are familiar with
- Only one repository to configure routing, security, etc.
Option #2
Single Page App (SPA)
Single Page App (SPA)
Cons
- Have to build an API
- Client Side Auth and Routing
- CORS
Pros
- Fast (No page refreshes)
- Interactive
- Lightweight, Cacheable Payloads
Option #3
cbInertia & Inertia.js
cbInertia & Inertia.js
Pros
- Single source of truth for routing, security, etc.
- SPA-like Performance
- No need for an API
- Framework-agnostic
No, really, what is cbInertia?
- Not a new framework, but the glue between frameworks
- Build a SPA without the API
- Use React, Vue, or Svelte as your View layer
- cbInertia is the ColdBox server-side adapter for Inertia.js
- Inertia.js was developed by Jonathan Reinink
- There are official server-side adapters for Laravel and Rails
- There are lots of community adapters (like ColdBox!)
- There are official client-side adapters for React, Vue, and Svelte
Background
Demo App
CFCasts
Getting Started
Client-side Setup
Setup
npm install @inertiajs/vue3 vue
npm install --save-dev vite coldbox-vite-plugin @vitejs/plugin-vue
Setup
// vite.config.js
import { defineConfig } from "vite";
import coldbox from "coldbox-vite-plugin";
import vue from "@vitejs/plugin-vue";
export default () => {
return defineConfig({
plugins: [
vue(),
coldbox({
input: ["resources/assets/js/app.js"],
refresh: true
})
]
});
};
Setup
// resources/assets/js/app.js
import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/vue3";
import { resolvePageComponent } from "coldbox-vite-plugin/inertia-helpers";
createInertiaApp({
id: "app",
title: title => `${title} | Quick Tailwind Inertia Template`,
resolve: name =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob("./Pages/**/*.vue")
),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
}
});
Folder Structure
Server-side Setup
Installation
box install cbInertia
Usage
Simple Render
// handlers/Dashboard.cfc
component {
property name="cbInertia" inject="Inertia@cbInertia";
function index( event, rc, prc ) {
variables.cbInertia.render( "Dashboard/Index" );
}
}
Vue Template
<!-- resources/assets/js/Pages/Dashboard/Index.vue -->
<template>
<div>
<h1>Dashboard</h1>
<p>Hey there! Welcome to my Dashboard!</p>
<div>
<Link href="/">Home Page</Link>
</div>
</div>
</template>
<script setup>
import { Link } from "@inertiajs/vue3";
</script>
Passing Props
// handlers/Users.cfc
component {
property name="cbInertia" inject="Inertia@cbInertia";
property name="userService" inject="quickService:User";
function index( event, rc, prc ) {
variables.cbInertia.render( "Users/Index", {
"users": variables.userService.asMemento().get()
} );
}
}
Accepting Props
<!-- resources/assets/js/Pages/Users/Index.vue -->
<template>
<div>
<h1>Users</h1>
<div>
<Link href="/users/new">
Create User
</Link>
</div>
<div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>
<Link :href="`/users/${user.id}/edit`">
<img v-if="user.photo" :src="user.photo">
{{ user.name }}
</Link>
</td>
<td>
<Link :href="`/users/${user.id}/edit`" tabindex="-1">
{{ user.email }}
</Link>
</td>
<td>
<Link :href="`/users/${user.id}/edit`" tabindex="-1">
{{ user.owner ? 'Owner' : 'User' }}
</Link>
</td>
</tr>
<tr v-if="users.length === 0">
<td class="border-t px-6 py-4" colspan="3">No users found.</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { Link } from "@inertiajs/vue3";
defineProps({ users: Array });
</script>
inertia() helper
// handlers/Users.cfc
component {
property name="userService" inject="quickService:User";
function index( event, rc, prc ) {
inertia().render( "Users/Index", {
"users": variables.userService.asMemento().get()
} );
}
}
inertia() render shorthand
// handlers/Users.cfc
component {
property name="userService" inject="quickService:User";
function index( event, rc, prc ) {
inertia( "Users/Index", {
"users": variables.userService.asMemento().all()
} );
}
}
Submitting Form Requests
<!-- resources/assets/js/Pages/Users/Create.vue -->
<template>
<div>
<h1>
<Link href="/users">Users</Link>
<span>/</span> Create
</h1>
<div>
<form @submit.prevent="form.post('/users')">
<div>
<TextInput v-model="form.firstName" label="First name" />
<TextInput v-model="form.lastName" label="Last name" />
<TextInput v-model="form.email" label="Email" />
<TextInput v-model="form.password" type="password" label="Password" />
<SelectInput v-model="form.owner" label="Owner">
<option :value="true">Yes</option>
<option :value="false">No</option>
</SelectInput>
</div>
<div>
<LoadingButton :loading="form.processing" type="submit">
Create User
</LoadingButton>
</div>
</form>
</div>
</div>
</template>
<script>
import Layout from "@/Shared/Layout.vue";
export default {
layout: Layout
}
</script>
<script setup>
import { Link } from "@inertiajs/vue3";
import LoadingButton from ""@/Shared/LoadingButton";
import SelectInput from "@/Shared/SelectInput";
import TextInput from "@/Shared/TextInput";
import { useForm } from ""@inertiajs/vue3";
const form = useForm({
firstName: null,
lastName: null,
email: null,
password: null,
owner: false,
photo: null,
});
</script>
Handling Form Requests
// handlers/Users.cfc
component {
function new( event, rc, prc ) {
inertia( "Users/Create" );
}
function create( event, rc, prc ) {
var data = validateOrFail( target = rc, constraints = {
"firstName": { "required": true, "size": "1..50" },
"lastName": { "required": true, "size": "1..50" },
"email": { "required": true, "size": "1..50", "type": "email" },
"password": { "required": false },
"owner": { "required": true, "type": "boolean" }
} );
auth().user().getAccount().users().create( {
"firstName" = data.firstName,
"lastName" = data.lastName,
"email" = data.email,
"password" = event.getValue( "password", "" ),
"owner" = data.owner
} );
relocate( "users" );
}
}
Sharing Data
// interceptors/InertiaShareInterceptor.cfc
component {
property name="cbInertia" inject="provider:Inertia@cbInertia";
property name="authService" inject="provider:AuthenticationService@cbauth";
function preProcess() {
variables.cbInertia.share( "auth", {
"user": function() {
return variables.authService.check() ?
variables.authService.user().getMemento() :
javacast( "null", "" );
}
} );
variables.cbInertia.share( "errors", function() {
return flash.get( "errors", {} );
} );
}
}
Layouts
<!-- resources/assets/js/Pages/Dashboard/Index.vue -->
<template>
<div>
<Head title="Dashboard" />
<h1>Dashboard</h1>
<p>Hey there! Welcome to the Dashboard.</p>
</div>
</template>
<script>
import Layout from "@/Shared/Layout";
export default {
layout: Layout
}
</script>
<script setup>
import { Head } from "@inertiajs/vue3";
</script>
Layouts
<!-- resources/assets/js/Shared/Layout.vue -->
<template>
<div>
<div>
<div>
<Link href="/">
<Logo width="120" height="28" />
</Link>
</div>
<div>
<div>{{ user.account.name }}</div>
</div>
</div>
<div>
<div>
<slot />
</div>
</div>
</div>
</template>
<script setup>
import Logo from "@/Shared/Logo";
import { computed } from "vue";
import { usePage } from "@inertiajs/vue3";
const page = usePage();
const user = computed(() => page.props.auth.user);
</script>
And More!
- Programmatic Navigation
- Asset Version Change Detection
- Scroll Regions
- Partial Reloads
- Nested Layouts
- Local State Cache
- Prop Transformation
Get Started
box coldbox create app skeleton=cbTemplate-quick-tailwind-inertia
Live Coding
Links
- https://inertiajs.com/
- https://forgebox.io/view/cbinertia
- https://forgebox.io/view/cbtemplate-quick-tailwind-inertia
CFCamp 2023: Get Your Front-end Moving with cbInertia
By Eric Peterson
CFCamp 2023: Get Your Front-end Moving with cbInertia
- 328