Tricks

Admin panel Turbolinks

Jun 13, 2022
Tina Hammar
Integration, Admin panel

You will have to override the app.blade.php file if you want to add Turbolinks to the Filament Admin panel.

Steps

  1. Install hotwire/turbo
  2. Create filament-turbo.js
  3. Add it to webpack.mix.js
  4. Publish and override Filament base.blade.php
npm install --save-dev @hotwired/turbo

(The paths are based on my application, you don't have to keep this file structure ;)

resources/filament/filament-turbo.js

import '../js/libs/turbo';

resources/js/libs/turbo.js

import * as Turbo from '@hotwired/turbo';
 
//start Livewire turbolinks, source https://github.com/livewire/turbolinks/blob/master/src/index.js v0.1.4
//removes the need for a cdn link in app.blade.php
if (typeof window.Livewire === 'undefined') {
throw 'Livewire Turbolinks Plugin: window.Livewire is undefined. Make sure @livewireScripts is placed above this script include'
}
 
let firstTime = true;
 
function wireTurboAfterFirstVisit () {
// We only want this handler to run AFTER the first load.
if (firstTime) {
firstTime = false
 
return
}
 
window.Livewire.restart()
 
window.Alpine && window.Alpine.flushAndStopDeferringMutations && window.Alpine.flushAndStopDeferringMutations()
}
 
function wireTurboBeforeCache() {
document.querySelectorAll('[wire\\:id]').forEach(function(el) {
const component = el.__livewire;
const dataObject = {
fingerprint: component.fingerprint,
serverMemo: component.serverMemo,
effects: component.effects,
};
el.setAttribute('wire:initial-data', JSON.stringify(dataObject));
});
 
window.Alpine && window.Alpine.deferMutations && window.Alpine.deferMutations()
}
 
document.addEventListener("turbo:load", wireTurboAfterFirstVisit)
document.addEventListener("turbo:before-cache", wireTurboBeforeCache);
 
document.addEventListener("turbolinks:load", wireTurboAfterFirstVisit)
document.addEventListener("turbolinks:before-cache", wireTurboBeforeCache);
 
Livewire.hook('beforePushState', (state) => {
if (! state.turbolinks) state.turbolinks = {}
})
 
Livewire.hook('beforeReplaceState', (state) => {
if (! state.turbolinks) state.turbolinks = {}
})
//end Livewire turbolinks
 
//start turbo-laravel, source https://github.com/tonysm/turbo-laravel/blob/main/stubs/resources/js/libs/alpine.js v1.1.0
document.addEventListener('turbo:before-render', () => {
let permanents = document.querySelectorAll('[data-turbo-permanent]');
let undos = Array.from(permanents).map(el => {
el._x_ignore = true;
return () => {
delete el._x_ignore;
};
});
 
document.addEventListener('turbo:render', function handler() {
while(undos.length) undos.shift()();
document.removeEventListener('turbo:render', handler);
});
});
//end turbo-laravel
 
export default Turbo;

webpack.mix.js

mix.js('resources/filament/filament-turbo.js', 'public/js').extract(['@hotwired/turbo'], 'js/vendor-turbo.js');

Publish Filament views. Keep only resources/views/vendor/filament/components/layouts/base.blade.php, it is not a recommended to keep files that you do not intend to override.

php artisan vendor:publish --tag=filament-views

You need to move the Filament scripts to the head tag and defer them.

 
@props([
'title' => null,
])
<!DOCTYPE html>
<html
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
dir="{{ __('filament::layout.direction') ?? 'ltr' }}"
class="filament antialiased bg-gray-100 js-focus-visible"
>
<head>
{{ \Filament\Facades\Filament::renderHook('head.start') }}
 
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
 
@foreach (\Filament\Facades\Filament::getMeta() as $tag)
{{ $tag }}
@endforeach
 
@if ($favicon = config('filament.favicon'))
<link rel="icon" href="{{ $favicon }}">
@endif
 
<title>{{ $title ? "{$title} - " : null }} {{ config('filament.brand') }}</title>
 
<style>
[x-cloak=""], [x-cloak="x-cloak"], [x-cloak="1"] { display: none !important; }
@media (max-width: 1023px) { [x-cloak="-lg"] { display: none !important; } }
@media (min-width: 1024px) { [x-cloak="lg"] { display: none !important; } }
</style>
 
@livewireStyles
 
@if (filled($fontsUrl = config('filament.google_fonts')))
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="{{ $fontsUrl }}" rel="stylesheet" />
@endif
 
@foreach (\Filament\Facades\Filament::getStyles() as $name => $path)
@if (Str::of($path)->startsWith(['http://', 'https://']))
<link rel="stylesheet" href="{{ $path }}" />
@else
<link rel="stylesheet" href="{{ route('filament.asset', [
'file' => "{$name}.css",
]) }}" />
@endif
@endforeach
 
<link rel="stylesheet" href="{{ \Filament\Facades\Filament::getThemeUrl() }}" />
 
@if (config('filament.dark_mode'))
<script>
const theme = localStorage.getItem('theme')
 
if ((theme === 'dark') || (! theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
}
</script>
@endif
 
@livewireScripts
<script src="{{ mix('js/manifest.js') }}" defer></script>
<script src="{{ mix('js/vendor-turbo.js') }}" defer></script>
<script src="{{ mix('js/filament-turbo.js') }}" defer></script>
 
<script>
window.filamentData = @json(\Filament\Facades\Filament::getScriptData());
</script>
 
@foreach (\Filament\Facades\Filament::getBeforeCoreScripts() as $name => $path)
@if (Str::of($path)->startsWith(['http://', 'https://']))
<script src="{{ $path }}" defer></script>
@else
<script src="{{ route('filament.asset', [
'file' => "{$name}.js",
]) }}" defer></script>
@endif
@endforeach
 
@stack('beforeCoreScripts')
 
<script src="{{ route('filament.asset', [
'id' => Filament\get_asset_id('app.js'),
'file' => 'app.js',
]) }}" defer></script>
 
@foreach (\Filament\Facades\Filament::getScripts() as $name => $path)
@if (Str::of($path)->startsWith(['http://', 'https://']))
<script src="{{ $path }}" defer></script>
@else
<script src="{{ route('filament.asset', [
'file' => "{$name}.js",
]) }}" defer></script>
@endif
@endforeach
 
@stack('scripts')
 
{{ \Filament\Facades\Filament::renderHook('head.end') }}
</head>
 
<body @class([
'bg-gray-100 text-gray-900 filament-body',
'dark:text-gray-100 dark:bg-gray-900' => config('filament.dark_mode'),
])>
{{ \Filament\Facades\Filament::renderHook('body.start') }}
 
{{ $slot }}
 
{{ \Filament\Facades\Filament::renderHook('body.end') }}
</body>
</html>

No comments yet…