composer require inertiajs/inertia-laravel
resources/views
içerisinde oluştur. Laravel'de bu varsayılan olarak app.blade.php
içine yazılması gerekir. Bu dosyada statik assetlerin ve en önemlisi @inertia
direktifinin olması gerekiyor.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
<script src="{{ mix('/js/app.js') }}" defer></script>
</head>
<body>
@inertia
</body>
</html>
Eğer app.blade.php
yerine başka bir dosya adı, başka bir klasör kullanmak istersen bunu Inertia::setRootView()
fonsiyonundan değiştirebilirsin.
php artisan inertia:middleware
App\Http\Kernel
dosyasında HandleInertiaRequest
'i web middleware group'uma ekliyorum.
'web' => [
// ...
\App\Http\Middleware\HandleInertiaRequests::class,
],
npm install vue@next
npm install @inertiajs/inertia @inertiajs/inertia-vue3
resources/js
klasöründe bulunan bootstrap.js
dosyasına ihtiyacımız yok. Silebiliriz.import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/inertia-vue3'
createInertiaApp({
resolve: name => require(`./Pages/${name}`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
burada gördüğümüz gibi Inertia.js web uygulamamızın sayfalarını resources/js/Pages
içerisinde arayacak. O nedenle bu klasörü de oluşturmamız gerekiyor.
webpack.mix.js
dosyasında app.js
dosyasının Vue 3 ile compile edilmesi gerektiğini söylüyoruz .vue(3)
ve browser cache problemi yaşamamak için .version()
fonksiyonunu kullanıyoruz.
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
.vue(3)
.postCss('resources/css/app.css', 'public/css', [
//
])
.version();
npm install
diyerek yüklüyoruz sonrasında da npx mix
diyerek webpack mix'i çalıştırıyoruz.<template>
<h1>Hello, {{ name }}</h1>
<ul>
<li v-for="framework of frameworks" v-text="framework"></li>
</ul>
</template>
<script>
export default {
props: {
name: String,
frameworks: Array,
}
}
</script>
Home.vue
olacağını söylemem gerekiyor. Backend tarafından datayı yolladığımızda bunu kullanmak için Vue tarafında prop olarak almamız gerekiyor.
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::get('/', function() {
return Inertia::render('Home', [
'name' => 'H. Omer Sensoy',
'frameworks' => ['Laravel', 'Vue', 'Inertia']
]);
});
mix watch
komutunu kullanıyorum.
npx mix watch
Route::get('/', function() {
return Inertia::render('Home');
});
Route::get('/users', function() {
return Inertia::render('Users');
});
Route::get('/settings', function() {
return Inertia::render('Settings');
});
<Link>
direktifini kullanmam gerekiyor.
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Users</a></li>
<li><a href="#">Settings</a></li>
</ul>
</nav>
bağlantıyı <a> olarak verirsen olmaz. Inertia'nın araya girmesini istiyorsak Inertia'nın <Link> komponentini kullanmamız lazım.
<template>
<nav>
<ul>
<li><Link href="/">Home</Link></li>
<li><Link href="/users">Users</Link></li>
<li><Link href="/settings">Settings</Link></li>
</ul>
</nav>
</template>
<script>
import { Link } from '@inertiajs/inertia-vue3'
export default {
components: { Link },
}
</script>
<template>
<nav>
<ul>
<li><Link href="/">Home</Link></li>
<li><Link href="/users">Users</Link></li>
<li><Link href="/settings">Settings</Link></li>
</ul>
</nav>
</template>
<script>
import { Link } from '@inertiajs/inertia-vue3'
export default {
components: { Link },
}
</script>
<template>
<h1>Home</h1>
<Nav />
</template>
<script>
import Nav from '../Shared/Nav'
export default {
components: { Nav },
}
</script>
aynısını Users.vue ve Settings.vue için de yap.
npm install @inertiajs/progress
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/inertia-vue3'
+ import { InertiaProgress } from '@inertiajs/progress'
createInertiaApp({
resolve: name => require(`./Pages/${name}`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
});
+ InertiaProgress.init();
...
Route::post('/logout', function () {
dd('Logged out user');
});
<Link>
ile yönlendirme yapmak istesek o zaman GET requesti yapmaya çalışacak... Hata verir! Bunun olmaması için method="post"
diye attribute eklememiz lazım.
<Link href="/logout" method="post">Logout</Link>
<Link href="/logout" method="post" as="button">Logout</Link>
:data
attribute'unu kullanabilirsin.
<Link href="/logout" method="post" :data="{foo:'bar'}" as="button">Logout</Link>
<Link href="/" preserve-scroll>Home</Link>
import { Link } from '@inertiajs/inertia-vue'
// URL tam olarak böyleyse aktive et
<Link href="/users" :class="{ 'active': $page.url === '/users' }">Users</Link>
// Komponent tam olarak böyleyse aktive et
<Link href="/users" :class="{ 'active': $page.component === 'Users/Index' }">Users</Link>
// URL bu şekilde başlıyorsa aktive et (/users, /users/create, /users/1, etc.)
<Link href="/users" :class="{ 'active': $page.url.startsWith('/users') }">Users</Link>
// Komponent bu şekilde başlıyor ise aktive et (Users/Index, Users/Create, Users/Show, etc.)
<Link href="/users" :class="{ 'active': $page.component.startsWith('Users') }">Users</Link>
<template>
<Nav />
<slot />
</template>
<script>
import Nav from './Nav'
export default {
components: { Nav },
}
</script>
<slot />
olarak belirlediğim yere, kullanıldığı sayfada <Layout></Layout>
içinde ne varsa o içerik olarak gelir. Örneğin Home.vue dosyasında:
<template>
<Layout>
<h1>Home</h1>
</Layout>
</template>
<script>
import Layout from '../Shared/Layout'
export default {
components: { Layout },
}
</script>
public function share(Request $request)
{
return array_merge(parent::share($request), [
'auth' => [
'user' => ['username' => 'John Doe']
]
])
}
<p>Welcome {{ $page.props.auth.user.username }}</p>
<template>
<Layout>
<h1>Home - Welcome {{ username }}</h1>
</Layout>
</template>
<script>
import Layout from '../Shared/Layout'
export default {
components: { Nav },
computed: {
username() {
return this.$page.auth.user.username;
}
}
}
</script>
class HandleInertiaRequests extends Middleware
{
public function share(Request $request)
{
return array_merge(parent::share($request), [
'flash' => [
'message' => fn () => $request->session()->get('message')
],
]);
// veya
return array_merge(parent::share($request), [
'flash' => function () use ($request) {
return [
'message' => $request->session()->get('message'),
];
}
]);
}
}
<template>
<main>
<header></header>
<content>
<div v-if="$page.props.flash.message" class="alert">
{{ $page.props.flash.message }}
</div>
<slot />
</content>
<footer></footer>
</main>
</template>
public function store()
{
User::create(
Request::validate([
'name' => ['required', 'max:50'],
'email' => ['required', 'email', 'max:50'],
'role' => ['required', 'max:50'],
])
)
return Redirect::route('users.index')->with(['message' => 'New user successfully created']);
}
+ import { createInertiaApp, Link } from "@inertiajs/inertia-vue3";
import { InertiaProgress } from "@inertiajs/progress";
createInertiaApp({
resolve: name => import(`./Pages/${name}`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props)})
.use(plugin)
+ .component('Link', Link)
.mount(el);
}
})
.component()
metodunu çağırabilirsin.<template>
- <Layout>
<h1>Home</h1>
- </Layout>
</template>
<script>
import Layout from '../Shared/Layout'
export default {
+ layout: Layout
}
</script>
createInertiaApp()
fonksiyonunda resolve ediyorken direk sayfa komponentini require etmek yerine onu bir değişkene atayıp .layout
özelliğine persist edecek Layout
komponentini tanımlayabiliriz.
import Layout from './Shared/Layout';
...
createInertiaApp({
resolve: name => {
let page = require(`./Pages/${name}`).default;
return page;
},
.default
componenti aldığımızı belirtmemiz lazım.import Layout from './Shared/Layout';
...
createInertiaApp({
resolve: name => {
let page = require(`./Pages/${name}`).default;
/*if(!page.layout) {
page.layout = Layout;
}*/
page.layout ??= Layout;
return page;
},
public function index()
{
$users = Users::all();
return Inertia::render('Users/Index', [
'users' => $users->map->only('id','name','email','role')
])
}
return Inertia::render('Users', [
'users' => User::all()->map(fn($user) => [
'name' => $user->name,
])
]);
arrow function Php 7.4'te çalışan bir özellik.
private function getMakamlar()
{
return Makam::orderBy('name')->get()->map(function($makam) {
return [
'name' => 'Makam: ' . $makam->slug,
];
});
}
private function getMeskler()
{
return Mesk::orderBy('date', 'desc')
->where('is_visible', true)
->paginate(50)
->through(function ($mesk) {
return [
'uri' => $mesk->date,
'date' => Carbon::parse($mesk->date)->translatedFormat('j F Y'),
];
});
}
routes/web.php
'de öncelikle create view ve post process routelarımızı belirtelim
Route::get('users/create', [UsersController::class, 'create']);
Route::post('users', [UsersController::class, 'store']);
public function create()
{
return Inertia::render('Users/Create);
}
<template>
<form @submit.prevent="submit">
<label for="name">Name:</label>
<input id="name" v-model="form.name" type="text" />
<label for="email">Email:</label>
<input id="email" v-model="form.email" type="email" />
<label for="role">Role:</label>
<select id="role" v-model="form.role">
<option />
<option>Admin</option>
<option>Owner</option>
<option>Guest</option>
</select>
<button type="submit">Create</button>
</form>
</template>
<script>
import { reactive } from 'vue';
import { Inertia } from '@inertiajs/inertia'
import { Layout } from '../Shared/Layout'
export default {
layout: Layout,
setup() {
const form = reactive({
name: null,
email: null,
role: null,
})
function submit() {
// submit handler...
}
return { form, submit }
}
}
</script>
submit() {
axios.post("/users", this.form)
.then(response => window.location.href = '/users')
.catch(error => {
this.errors = error;
});
}
submit() {
Inertia.post('/users', form)
}
public function store()
{
User::create(
Request::validate([
'name' => ['required', 'max:50'],
'email' => ['required', 'email', 'max:50'],
'role' => ['required', 'in:Owner,Admin,Guest'],
])
)
return Redirect::route('users.index')->with(['message' => 'New user successfully created']);
}
<template>
<form @submit.prevent="form.post('/users')">
<!-- name -->
<label for="name">Name:</label>
<input id="name" v-model="form.name" type="text" />
<div v-if="form.errors.name">{{ form.errors.name }}</div>
<!-- email -->
<label for="email">Email:</label>
<input id="email" v-model="form.email" type="email" />
<div v-if="form.errors.email">{{ form.errors.email }}</div>
<!-- role -->
<label for="role">Role:</label>
<select id="role" v-model="form.role">
<option />
<option>Admin</option>
<option>Owner</option>
<option>Guest</option>
</select>
<div v-if="form.errors.role">{{ form.errors.role }}</div>
<!-- submit -->
<button type="submit" :disabled="form.processing">Login</button>
</form>
</template>
<script>
import { useForm } from '@inertiajs/inertia-vue3'
export default {
setup () {
const form = useForm({
name: null,
email: null,
role: null,
})
return { form }
},
}
</script>
import { useRemember } from '@inertiajs/inertia-vue3'
export default {
setup() {
const form = useRemember({
first_name: null,
last_name: null,
})
return { form }
},
}
mix.js('resources/js/app.js', 'public/js')
.extract()
.vue(3)
.postCss('resources/css/twapp.css', 'public/css', [
require("tailwindcss"),
])
extract()
fonksiyonu common vendor dependency'leri app.js kodumun dışına almamı sağlar. Böylece app.js
güncellendiğinde vendor.js
'in cache'li versiyonu hala çalışır durumda kalır ve tekrar yüklemek zorunda kalmayız. Buna simple vendor extraction deniyor.npx mix
dediğimde 2 tane daha dosyam oluşur. manifest.js (webpack manifest dosyası) ve vendor.js (vendor dependency dosyası)<script src="{{ mix('/js/manifest.js') }}" defer></script>
<script src="{{ mix('/js/vendor.js') }}" defer></script>
<script src="{{ mix('/js/app.js') }}" defer></script>
app.js
dosyasında async olarak name fonksiyonunu çalıştırıyorum. Ve import işlemi tamamlandığında await ile gerekli komponenti alıyorum.
createInertiaApp({
resolve: async name => {
let page = (await import(`./Pages/${name}`)).default;
page.layout ??= AppShell;
return page;
},
<template>
<Head>
<title>Mûsikî Meşkleri</title>
</Head>
...
</template>
<script>
import { Head } from '@inertiajs/inertia-vue3';
...
export default {
components: {
Head,
head-key
diye bir attribute koymamız yeterli. Aynı head-key
hem Layout hem de sayfa komponentinde kullanıldığında Inertia hangi tag'in hangisinin aynısı olduğunu anlar.
<Head>
<title>Mûsikî Meşkleri</title>
<meta type="description" content="Tasavvuf musikisi meşkleri" head-key="description">
</Head>
createInertiaApp({
resolve: name => {
let page = require(`./Pages/${name}`).default;
page.layout ??= AppShell;
return page;
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
title: title => `Dilbeyti - ${title}`,
});
createInertiaApp({
...
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.use(store)
.component("Head", Head)
.mount(el)
},
...
$users = Users::paginate(15);
diyor ve Vue sayfasına veri olarak atıyorsak artık Vue tarafında bunu prop olarak kabul ederken Array değil, Object olarak almalıyız ve o anki sayfanın listesi de users.data
içerisinde bulunur.users.links
içindedir. Sayfada listenin altında paginator'u bu links listesinden oluşturabilirim.
<div>
<Link v-for="link in meskler.links" :href="link.url" v-html="link.label" />
</div>
<div>
<template v-for="link in meskler.links">
<Link v-if="link.url" :href="link.url" v-html="link.label" />
<span v-else v-html="link.label"></span>
</template>
</div>
Eğer link elementinin url'si varsa o zaman Inertia Link componenti render eder, yoksa SPAN render eder. BU 1. YOLdu.
<Component :is="link.url ? 'Link' : 'span'"
v-for="link in meskler.links"
:href="link.url"
v-html="link.label"
class="px-1"
:class="link.url ? '' : 'text-gray-400'"></Component>
<template>
<div>
<Component :is="link.url ? 'Link' : 'span'"
v-for="link in links"
:href="link.url"
v-html="link.label"
class="px-1"
:class="link.url ? '' : 'text-red-400'"></Component>
</div>
</template>
<script>
export default {
props: {
links: Array,
}
}
</script>
<template>
...
<Pagination :links="meskler.links" class="mt-6"></Pagination>
...
</template>
<script>
import Pagination from "../../Shared/Pagination";
export default {
components: {
Pagination,
...
</script>
private function getMeskler()
{
return Mesk::orderBy('date','desc')
->where('is_visible',true)
->paginate(20)
->through(function($mesk) {
return [
'uri' => $mesk->miladi,
'date' => Carbon::parse($mesk->miladi)->translatedFormat('j F Y'),
'hicri_date' => $mesk->hicri,
];
});
}
<Component :is="link.url ? 'Link' : 'span'"
v-for="link in links"
:href="link.url"
v-html="link.label"
class="px-1"
:class="{'text-gray-500': !link.url, 'font-bold underline': link.active}"></Component>
<input type="search"
v-model="search"
placeholder="Meşkler içinde ara..."
class="p-2 border-gray-200 border rounded">
<script setup>
import { ref, watch } from "vue";
...
let search = ref('');
watch(search, value => {
console.log(value);
})
...
</script>
<Link>
componenti ile yapıyorduk. Halbuki program içinde de Manuel visit olarak yapabiliriz. Bunun için Inertia'yı import ediyoruz ve kullanıyoruz.
<script setup>
import { ref, watch } from "vue";
...
import { Inertia } from "@inertiajs/inertia";
...
let search = ref('');
watch(search, value => {
Inertia.get('/meskler', {search: value});
})
...
</script>
return Mesk::query()
->when(request('search'), function($query, $search) {
$query->where('date', 'like', "%{$search}%");
})
->paginate(50)
->through(function ($mesk) {
return [
'date' => Carbon::parse($mesk->date)->translatedFormat('j F Y'),
];
});
when($sorgu_değeri, doğru olursa/varsa uygulanacak fonksiyon)... Buradaki fonksiyonda 1. parametre Query'nin kendisi, 2. parametre(opsiyonel) sorgu değeridir. İçeride tekrar kullanılacaksa burada yazmak faydalı oluyor.
return Mesk::query()
->when(request('search'), function($query, $search) {
$query->where('date', 'like', "%{$search}%");
})
->paginate(50)
->withQueryString()
->through(function ($mesk) {
return [
'date' => Carbon::parse($mesk->date)->translatedFormat('j F Y'),
];
});
Inertia.get(url, parametre, opsiyonlar)
opsiyonlar kısmına preserveState: true
dememiz gerekir.
watch(search, value => {
Inertia.get('/meskler', {search: value}, {
preserveState: true,
});
})
let search = ref('');
Backend tarafından arama terimini sayfaya prop olarak yollarsak, Vue tarafında bunu prop olarak kabul edip, search değerini boş başlatmak yerine o değerle başlatabiliriz. Backend Controller'da:
use Illuminate\Support\Facades\Request;
...
return Inertia::render('Mesk/Index', [
'meskler' => $this->getMeskler(),
'filters' => Request::only(['search'])
]);
Vue tarafında prop olara al ve search değişkenini o değerle başlat
let props = defineProps({
meskler: Object,
filters: Object,
});
let search = ref(props.filters.search);
watch(search, value => {
Inertia.get('/meskler', {search: value}, {
preserveState: true,
replace: true,
});
})