Get Your Front-end Moving with cbInertia
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
What is cbInertia?
Why am I learning about another JavaScript framework?
Today's Web Apps
- Fast
- Small
- 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
- Small, 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 server-side adapters for Laravel and Rails
- There are client-side adapters for React, Vue, and Svelte
Background
Demo App
Getting Started
Client-side Setup
Setup
npm install @inertiajs/inertia @inertiajs/inertia-vue vue
npm install --save-dev coldbox-elixir @babel/plugin-syntax-dynamic-import
Setup
// webpack.config.js
const elixir = require("coldbox-elixir");
// used for code splitting
elixir.config.mergeConfig({
optimization: {
splitChunks: {
cacheGroups: {
shared: {
chunks: "async",
minChunks: 2,
name: "includes/js/pages/shared"
}
}
}
}
});
module.exports = elixir(mix => {
mix.vue("app.js");
});
Setup
// resources/assets/js/app.js
import Vue from "vue";
import { InertiaApp } from "@inertiajs/inertia-vue";
Vue.use(InertiaApp);
let app = document.getElementById("app");
new Vue({
render: h =>
h(InertiaApp, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: name =>
import(
/* webpackChunkName: "includes/js/pages/[request]" */ `@/Pages/${name}`
).then(module => module.default)
}
})
}).$mount(app);
Folder Structure
Server-side Setup
Installation
box install cbInertia
Usage
Simple Render
// handlers/Dashboard.cfc
component {
property name="inertia" inject="@cbInertia";
function index( event, rc, prc ) {
inertia.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>
<inertia-link href="/">Home Page</inertia-link>
</div>
</div>
</template>
<script>
export default {}
</script>
Passing Props
// handlers/Users.cfc
component {
property name="inertia" inject="@cbInertia";
property name="userService" inject="quickService:User";
function index( event, rc, prc ) {
inertia.render( "Users/Index", {
"users": userService.asMemento().all()
} );
}
}
Accepting Props
// resources/assets/js/Pages/Users/Index.vue
<template>
<div>
<h1>Users</h1>
<div>
<inertia-link href="/users/new">
Create User
</inertia-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>
<inertia-link :href="`/users/${user.id}/edit`">
<img v-if="user.photo" :src="user.photo">
{{ user.name }}
</inertia-link>
</td>
<td>
<inertia-link :href="`/users/${user.id}/edit`" tabindex="-1">
{{ user.email }}
</inertia-link>
</td>
<td>
<inertia-link :href="`/users/${user.id}/edit`" tabindex="-1">
{{ user.owner ? 'Owner' : 'User' }}
</inertia-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>
export default {
props: {
users: {
type: Array,
required: true
},
}
}
</script>
inertia() helper
// handlers/Users.cfc
component {
property name="userService" inject="quickService:User";
function index( event, rc, prc ) {
inertia().render( "Users/Index", {
"users": userService.asMemento().all()
} );
}
}
inertia() render shorthand
// handlers/Users.cfc
component {
property name="userService" inject="quickService:User";
function index( event, rc, prc ) {
inertia( "Users/Index", {
"users": userService.asMemento().all()
} );
}
}
Submitting Form Requests
// resources/assets/js/Pages/Users/Create.vue
<template>
<div>
<h1>
<inertia-link href="/users">Users</inertia-link>
<span>/</span> Create
</h1>
<div>
<form @submit.prevent="submit">
<div>
<TextInput v-model="form.first_name" label="First name" />
<TextInput v-model="form.last_name" 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="sending" type="submit">Create User</loading-button>
</div>
</form>
</div>
</div>
</template>
<script>
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
export default {
components: {
LoadingButton,
SelectInput,
TextInput,
},
data() {
return {
sending: false,
form: {
first_name: null,
last_name: null,
email: null,
password: null,
owner: false,
photo: null,
},
}
},
methods: {
submit() {
this.sending = true
var data = new FormData()
data.append('first_name', this.form.first_name || '')
data.append('last_name', this.form.last_name || '')
data.append('email', this.form.email || '')
data.append('password', this.form.password || '')
data.append('owner', this.form.owner ? '1' : '0')
this.$inertia.post("/users", data, {
onFinish: () => {
this.sending = false;
}
});
},
},
}
</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 = {
"first_name": { "required": true, "size": "1..50" },
"last_name": { "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( {
"first_name" = data.first_name,
"last_name" = data.last_name,
"email" = data.email,
"password" = event.getValue( "password", "" ),
"owner" = data.owner
} );
relocate( "users" );
}
}
Sharing Data
// interceptors/InertiaShareInterceptor.cfc
component {
property name="inertia" inject="provider:Inertia@cbInertia";
property name="auth" inject="provider:AuthenticationService@cbauth";
function preProcess() {
inertia.share( "auth", {
"user": function() {
return auth.check() ?
auth.user().getMemento() :
javacast( "null", "" );
}
} );
inertia.share( "errors", function() {
return flash.get( "errors", {} );
} );
}
}
Layouts
// resources/assets/js/Pages/Dashboard/Index.vue
<template>
<div>
<h1>Dashboard</h1>
<p>Hey there! Welcome to the Dashboard.</p>
</div>
</template>
<script>
import Layout from '@/Shared/Layout'
export default {
metaInfo: { title: 'Dashboard' },
layout: Layout,
}
</script>
Layouts
// resources/assets/js/Shared/Layout.vue
<template>
<div>
<div>
<div>
<inertia-link href="/">
<Logo width="120" height="28" />
</inertia-link>
</div>
<div>
<div>{{ $page.props.auth.user.account.name }}</div>
</div>
</div>
<div>
<div scroll-region>
<slot />
</div>
</div>
</div>
</template>
<script>
import Logo from '@/Shared/Logo'
export default {
components: {
Logo
}
}
</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
Get Your Front-end Moving with cbInertia
By Eric Peterson
Get Your Front-end Moving with cbInertia
- 1,044