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
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
MatthewOP2y 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
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
MatthewOP2y ago
I see, thank you. But how can I handle the play/pause dispatch events?
Patrick Boivin
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
MatthewOP2y ago
This is how I approached it in just alpine:
<!DOCTYPE html>
<html>

<head>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/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/[email protected]/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
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
MatthewOP2y ago
I see. Thank you! 🙂 I will keep you updated
awcodes
awcodes2y ago
if the alpine works, why can't you just put that code in the blade file. Alpine is already loaded
Matthew
MatthewOP2y 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
awcodes2y ago
change the 'body' tags to 'div' you can't nest body tags
Matthew
MatthewOP2y 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
awcodes2y 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
MatthewOP2y 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
MatthewOP2y 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
MatthewOP2y 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
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
MatthewOP2y ago
I doubt it. It works fine for other files like svg, png etc
Patrick Boivin
Ok. Any errors in the browser console or storage/logs/laravel.log ?
Matthew
MatthewOP2y ago
No description
Matthew
MatthewOP2y ago
I exceeded the default limit?
Patrick Boivin
Php upload limit probably in php.ini
Matthew
MatthewOP2y ago
Yes you were right. I just went to the ini file and edited the default values. Thanks:)

Did you find this page helpful?