F
Filament11mo ago
Matthew

How can I run Alpine.js code from php or blade?

I want to create a music player, and even though the plain alpine code works, I need to intergrade it in blade or php.
23 Replies
Patrick Boivin
Patrick Boivin11mo ago
I think the main way to interact between Livewire and Alpine would be dispatching events... can you give a bit more context on what you're trying to do?
Matthew
Matthew11mo ago
Yes so basically I want to create my own music player plugin for filament. Atm I want to see how I can make a single audio file play. And afterwards I will make a UI for it. For example:
<x-filament-panels::page>
<div>
{{ $this->deleteAction }}

<x-filament-actions::modals />
</div>
<audio controls>
<source src="storage/songs/Palikari_frikio.wav" type="audio/mpeg">
</audio>
</x-filament-panels::page>
<x-filament-panels::page>
<div>
{{ $this->deleteAction }}

<x-filament-actions::modals />
</div>
<audio controls>
<source src="storage/songs/Palikari_frikio.wav" type="audio/mpeg">
</audio>
</x-filament-panels::page>
<?php

namespace App\Filament\Pages;
use Filament\Actions\Action;

use Filament\Pages\Page;

class SortUsers extends Page
{
protected static ?string $title = 'Music';
protected static ?string $navigationLabel = 'Music';
protected static ?string $slug = 'music';

protected static ?string $navigationIcon = 'heroicon-o-document-text';

protected static string $view = 'filament.pages.sort-users';

public function deleteAction(): Action
{
return Action::make('delete')
->requiresConfirmation();
}
}
<?php

namespace App\Filament\Pages;
use Filament\Actions\Action;

use Filament\Pages\Page;

class SortUsers extends Page
{
protected static ?string $title = 'Music';
protected static ?string $navigationLabel = 'Music';
protected static ?string $slug = 'music';

protected static ?string $navigationIcon = 'heroicon-o-document-text';

protected static string $view = 'filament.pages.sort-users';

public function deleteAction(): Action
{
return Action::make('delete')
->requiresConfirmation();
}
}
So the Action in this case wont be delete, but lets say play When you press play, the music starts Then I need to make another Action that pauses the music etc
Patrick Boivin
Patrick Boivin11mo ago
I see! Thanks. So a few thoughts: The <audio> tag will have its own set of controls... as far as I know it's not very styleable in CSS but you can style the container. There are entire JS libraries to implement custom video/audio players, it's not a small task. But totally possible. This is inevitably linked to browser permissions, so if a user doesn't give permissions to play audio/video, you custom button won't be able to start/stop the audio player.
Matthew
Matthew11mo ago
I see, thank you. But how can I handle the play/pause dispatch events?
Patrick Boivin
Patrick Boivin11mo ago
But to answer your question more directly, I think I would dispatch a browser event from the Filament action, and catch it with an Alpine listener on the <audio> tag.
Matthew
Matthew11mo ago
This is how I approached it in just alpine:
<!DOCTYPE html>
<html>

<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>

<body class="bg-aqua" x-data="{ audiofile: null, isPlaying: false }">
<div class="flex justify-center items-center min-h-screen">
<button class="button" @click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" @click="pauseAudio">Pause</button>
<div>
<input type="file" @change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>

<script>
function playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
}

function pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
}

function onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
</script>
</body>

</html>
<!DOCTYPE html>
<html>

<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>

<body class="bg-aqua" x-data="{ audiofile: null, isPlaying: false }">
<div class="flex justify-center items-center min-h-screen">
<button class="button" @click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" @click="pauseAudio">Pause</button>
<div>
<input type="file" @change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>

<script>
function playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
}

function pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
}

function onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
</script>
</body>

</html>
I assume there is filament documentation for that, correct?
Patrick Boivin
Patrick Boivin11mo ago
JS code looks fine at a glance, here's a rough sketch of what I'm thinking about:
->action(function ($livewire) {
$livewire->dispatch('play-audio');
})
->action(function ($livewire) {
$livewire->dispatch('play-audio');
})
<audio
x-data="{}"
x-on:play-audio.window="$el.play()" // or something similar, I don't actually remember

...
>
<audio
x-data="{}"
x-on:play-audio.window="$el.play()" // or something similar, I don't actually remember

...
>
This is more in the Livewire/Alpine side of things... I would look in the docs for that instead of Filament
Matthew
Matthew11mo ago
I see. Thank you! 🙂 I will keep you updated
awcodes
awcodes11mo ago
if the alpine works, why can't you just put that code in the blade file. Alpine is already loaded
Matthew
Matthew11mo ago
I tried but for some reason it doesnt play:
<x-filament-panels::page>
<body class="bg-aqua" x-data="{ audiofile: null, isPlaying: false }">
<div class="flex justify-center items-center min-h-screen">
<button class="button" @click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" @click="pauseAudio">Pause</button>
<div>
<input type="file" @change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>

<script>
function playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
}

function pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
}

function onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
</script>
</body>
</x-filament-panels::page>
<x-filament-panels::page>
<body class="bg-aqua" x-data="{ audiofile: null, isPlaying: false }">
<div class="flex justify-center items-center min-h-screen">
<button class="button" @click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" @click="pauseAudio">Pause</button>
<div>
<input type="file" @change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>

<script>
function playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
}

function pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
}

function onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
</script>
</body>
</x-filament-panels::page>
Im almost sure I havent configured blade properly though
awcodes
awcodes11mo ago
change the 'body' tags to 'div' you can't nest body tags
Matthew
Matthew11mo ago
Yeah, I just tried and it doesnt work. No audio
<x-filament-panels::page>
<div>
{{ $this->deleteAction }}

<x-filament-actions::modals />
</div>

<div class="bg-aqua" x-data="{ audiofile: null, isPlaying: false }">
<div class="flex justify-center items-center min-h-screen">
<button class="button" @click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" @click="pauseAudio">Pause</button>
<div>
<input type="file" @change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>
</div>

<script>
function playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
}

function pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
}

function onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
</script>
</x-filament-panels::page>
<x-filament-panels::page>
<div>
{{ $this->deleteAction }}

<x-filament-actions::modals />
</div>

<div class="bg-aqua" x-data="{ audiofile: null, isPlaying: false }">
<div class="flex justify-center items-center min-h-screen">
<button class="button" @click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" @click="pauseAudio">Pause</button>
<div>
<input type="file" @change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>
</div>

<script>
function playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
}

function pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
}

function onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
</script>
</x-filament-panels::page>
awcodes
awcodes11mo ago
try this not sure why your control functions are outside the alpine component?
<div class="bg-aqua" x-data="{
audiofile: null,
isPlaying: false,
playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
},
pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
},
onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
}">
<div class="flex justify-center items-center min-h-screen">
<button class="button" x-on:click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" x-on:click="pauseAudio">Pause</button>
<div>
<input type="file" x-on:change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>
</div>
<div class="bg-aqua" x-data="{
audiofile: null,
isPlaying: false,
playAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.play();
this.isPlaying = true;
}
},
pauseAudio() {
if (this.audiofile) {
const audio = this.$refs.play_audio;
audio.pause();
this.isPlaying = false;
}
},
onFileChange(event) {
this.audiofile = URL.createObjectURL(event.target.files[0]);
}
}">
<div class="flex justify-center items-center min-h-screen">
<button class="button" x-on:click="playAudio" :disabled="!audiofile">Play</button>
<button class="button" x-on:click="pauseAudio">Pause</button>
<div>
<input type="file" x-on:change="onFileChange" accept=".mp3, .wav">
<span x-show="isPlaying && audiofile">Audio is playing...</span>
<audio x-ref="play_audio">
<source :src="audiofile" type="audio/mpeg" />
</audio>
</div>
</div>
</div>
Matthew
Matthew11mo ago
This didnt work either I think im getting closer. This works
<x-filament-panels::page>
<div>
{{ $this->deleteAction }}

<x-filament-actions::modals />
</div>

<audio controls
x-data="{}"
x-on:play-audio.window="$el.play()"
src="{{ asset('storage/Palikari_frikio.wav') }}" type="audio/mpeg"
></audio>
</x-filament-panels::page>
<x-filament-panels::page>
<div>
{{ $this->deleteAction }}

<x-filament-actions::modals />
</div>

<audio controls
x-data="{}"
x-on:play-audio.window="$el.play()"
src="{{ asset('storage/Palikari_frikio.wav') }}" type="audio/mpeg"
></audio>
</x-filament-panels::page>
At least it loads the song
Matthew
Matthew11mo ago
However when trying to upload to the storage, I got errors. I had to manually copy paste it. Any idea why?
No description
Matthew
Matthew11mo ago
public static function form(Form $form): Form
{
return $form
->schema([
FileUpload::make("attachment")
->disk("public")
]);
}
public static function form(Form $form): Form
{
return $form
->schema([
FileUpload::make("attachment")
->disk("public")
]);
}
Patrick Boivin
Patrick Boivin11mo ago
Possibly because of the temporary storage... you'll need to save the form (or at least trigger validation) for the file to be moved to the final storage location.
Matthew
Matthew11mo ago
I doubt it. It works fine for other files like svg, png etc
Patrick Boivin
Patrick Boivin11mo ago
Ok. Any errors in the browser console or storage/logs/laravel.log ?
Matthew
Matthew11mo ago
No description
Matthew
Matthew11mo ago
I exceeded the default limit?
Patrick Boivin
Patrick Boivin11mo ago
Php upload limit probably in php.ini
Matthew
Matthew11mo ago
Yes you were right. I just went to the ini file and edited the default values. Thanks:)