Laracasts Alpine code in an action

Im following a guide on laracasts and im at a point for it has a form component, and has attributes for: x-data, x-on:submit.prevent. I am wondering if I can use a custom action to call the alpinejs function from my app.js file, rather than creating a new blade file etc Bit lost trying to find this in the docs?
Solution:
probably something like this ->alpineClickHandler('register(xx)')
Jump to solution
27 Replies
Jamie Cee
Jamie Cee3mo ago
Anyone have any idea?
LeandroFerreira
LeandroFerreira3mo ago
please provide mor info what you are trying to do..
Jamie Cee
Jamie Cee3mo ago
So I have a function in my app.js called 'registerPasskeys' that has some js logic. I want to have an action that fires off that js function
Tables\Actions\Action:make('my action')
->action(fire my js here);
Tables\Actions\Action:make('my action')
->action(fire my js here);
LeandroFerreira
LeandroFerreira3mo ago
if you register your js file, you can call this using ->alpineClickHandler('registerPasskeys')
Jamie Cee
Jamie Cee3mo ago
Ill give that a go when im back in the office tomorrow, thank you Morning, I've just managed to have a test of this, im getting 'registerPasskey' is not defined, and im not sure the correct syntax.
document.addEventListener('alpine:init', () => {
Alpine.data('registerPasskey', () => ({
async register() {
const options = await axios.get('api/v1/passkeys/register');
const passkey = await startRegistration(options.data);

// console.log(options.data);
},
}))
});
document.addEventListener('alpine:init', () => {
Alpine.data('registerPasskey', () => ({
async register() {
const options = await axios.get('api/v1/passkeys/register');
const passkey = await startRegistration(options.data);

// console.log(options.data);
},
}))
});
The above is my alpinejs code. I tried registerPasskey, registerPasskey.register and just register, none of which work It looks like its searching the livewire.js file but I have it defined in my app.js file. So moved code into a custom.js file, Registered in service provider:
/* Register the js file, use vite facade because vite hashes the file name in build */
FilamentAsset::register([
Js::make('custom-script', Vite::asset('resources/js/custom.js')),
]);
/* Register the js file, use vite facade because vite hashes the file name in build */
FilamentAsset::register([
Js::make('custom-script', Vite::asset('resources/js/custom.js')),
]);
When I refresh browser, I initially get this:
SyntaxError: Cannot use import statement outside a module (at custom-DEbtIa2Z.js:1:1)
SyntaxError: Cannot use import statement outside a module (at custom-DEbtIa2Z.js:1:1)
And when I click my button, its still searching in the livewire.js file instead of my custom.js
LeandroFerreira
LeandroFerreira3mo ago
Are you using import axios in your custom JS?
Jamie Cee
Jamie Cee3mo ago
Yeah Custom.js
import Alpine from 'alpinejs';
import axios from 'axios';
import { startRegistration } from "@simplewebauthn/browser";

document.addEventListener('alpine:init', () => {
console.log('Alpine initializing...');
Alpine.data('registerPasskey', () => ({
async register() {
console.log('Register function called');
try {
const options = await axios.get('/api/v1/passkeys/register');
console.log('Options received:', options.data);
await startRegistration(options.data);
} catch (error) {
console.error('Error during passkey registration:', error);
}
},
}));
});
import Alpine from 'alpinejs';
import axios from 'axios';
import { startRegistration } from "@simplewebauthn/browser";

document.addEventListener('alpine:init', () => {
console.log('Alpine initializing...');
Alpine.data('registerPasskey', () => ({
async register() {
console.log('Register function called');
try {
const options = await axios.get('/api/v1/passkeys/register');
console.log('Options received:', options.data);
await startRegistration(options.data);
} catch (error) {
console.error('Error during passkey registration:', error);
}
},
}));
});
I see the "alpine initializing" console log, so the file is being read.
Jamie Cee
Jamie Cee3mo ago
Just when I click the button I get this
No description
LeandroFerreira
LeandroFerreira3mo ago
Try to import via cdn instead of
Jamie Cee
Jamie Cee3mo ago
Does that mean like this:
FilamentAsset::register([
Js::make('custom', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js')->module(true),
// Js::make('custom', Vite::asset('resources/js/custom.js'))->module(true),
]);
FilamentAsset::register([
Js::make('custom', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js')->module(true),
// Js::make('custom', Vite::asset('resources/js/custom.js'))->module(true),
]);
As thats spat out a whole lot of errors in dev tools,
No description
Jamie Cee
Jamie Cee3mo ago
No description
Jamie Cee
Jamie Cee3mo ago
If I move the logic outside of the Alpine.data function, it works. So its something to do with the Alpine.data not registering it as a component or its registered but the alpineClickHandler cant detect it.
LeandroFerreira
LeandroFerreira3mo ago
sorry for the delay.. where are you using this action? register axios and simplewebauthn via cdn in your appserviceprovider
FilamentAsset::register([
Js::make('axios', 'https://unpkg.com/[email protected]/dist/axios.min.js'),
Js::make('simplewebauthn', 'https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.es5.umd.min.js'),
Js::make('custom-script', __DIR__.'/../../resources/js/custom.js'),
]);
FilamentAsset::register([
Js::make('axios', 'https://unpkg.com/[email protected]/dist/axios.min.js'),
Js::make('simplewebauthn', 'https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.es5.umd.min.js'),
Js::make('custom-script', __DIR__.'/../../resources/js/custom.js'),
]);
Try this
Action::make('customAction')
->extraAttributes(['x-data' => 'registerPasskey'])
->alpineClickHandler('register')
Action::make('customAction')
->extraAttributes(['x-data' => 'registerPasskey'])
->alpineClickHandler('register')
don't forget to run php artisan filament:assets to publish the assets
Jamie Cee
Jamie Cee3mo ago
Sorry, I got caught in a meeting. So the action is in a relationmanager, in the headerActions array. I've added the code you linked above and got the following errors. The first 2 are just because Ill have to add the urls to the cors part of my docker container. I got a cannot use import statement outside a module error, then that the registerPasskey is not defined. This again being my code:
import Alpine from 'alpinejs';
import axios from 'axios';
import { startRegistration } from "@simplewebauthn/browser";

// const callRegisterPasskey = async () => {
// try {
// const options = await axios.get('/passkeys/register');
// console.log('Options received:', options.data);
// await startRegistration(options.data);
// } catch (error) {
// console.error('Error during passkey registration:', error);
// }
// }

document.addEventListener('alpine:init', () => {
console.log('Alpine initializing...');

// callRegisterPasskey();

Alpine.data('registerPasskey', () => ({
async register() {
// callRegisterPasskey();
try {
const options = await axios.get('/passkeys/register');
console.log('Options received:', options.data);
await startRegistration(options.data);
} catch (error) {
console.error('Error during passkey registration:', error);
}
},
}));

});
import Alpine from 'alpinejs';
import axios from 'axios';
import { startRegistration } from "@simplewebauthn/browser";

// const callRegisterPasskey = async () => {
// try {
// const options = await axios.get('/passkeys/register');
// console.log('Options received:', options.data);
// await startRegistration(options.data);
// } catch (error) {
// console.error('Error during passkey registration:', error);
// }
// }

document.addEventListener('alpine:init', () => {
console.log('Alpine initializing...');

// callRegisterPasskey();

Alpine.data('registerPasskey', () => ({
async register() {
// callRegisterPasskey();
try {
const options = await axios.get('/passkeys/register');
console.log('Options received:', options.data);
await startRegistration(options.data);
} catch (error) {
console.error('Error during passkey registration:', error);
}
},
}));

});
No description
LeandroFerreira
LeandroFerreira3mo ago
remove imports and register via cdn in your provider
Jamie Cee
Jamie Cee3mo ago
Apologies, I'd finished work at this point yesterday, So tried it this morning, removed the import lines from my js, added the cdn in my service provider, still to no avail
No description
Jamie Cee
Jamie Cee3mo ago
YESSS! Nevermind, it worked. The last part was the custom line in the provider, I had to change it back to Vite::asset(). And it now works. 2 days of stress, thank you so much. I can continue the rest of the course now 🤣 Would you mind explaining what those changes did exactly so I have an understanding of what went on
LeandroFerreira
LeandroFerreira3mo ago
Filament supports importing from CDN. When using Alpine.data, it's necessary to specify the component object in x-data, as outlined in the docs.
Jamie Cee
Jamie Cee3mo ago
Ah that makes sense, and so if when I call the register function, I want to pass a parameter, how would I do that?
LeandroFerreira
LeandroFerreira3mo ago
I believe you can pass params.. check the alpine docs
Jamie Cee
Jamie Cee3mo ago
Ah sorry, I mean from the alpineClickHandler point of view, is that just comma separated from the handler string? Sorry if these questions are dumb founded, just new to all of this
Solution
LeandroFerreira
LeandroFerreira3mo ago
probably something like this ->alpineClickHandler('register(xx)')
Jamie Cee
Jamie Cee3mo ago
Got it thank you. Thank you for your patience with me 🙂
LeandroFerreira
LeandroFerreira3mo ago
no problem!
Jamie Cee
Jamie Cee2mo ago
Sorry for coming back to this one, so we left off with an action that works and looks like this...
Forms\Components\Actions\Action::make('security_key')
->extraAttributes(['x-data' => 'registerPasskey', 'x-show' => "browserSupportsWebAuthn()"])
->alpineClickHandler('register("' . Str::random(10) . '")')
->label('Create Passkey'),
Forms\Components\Actions\Action::make('security_key')
->extraAttributes(['x-data' => 'registerPasskey', 'x-show' => "browserSupportsWebAuthn()"])
->alpineClickHandler('register("' . Str::random(10) . '")')
->label('Create Passkey'),
Im wondering if its possible for an equivalent work to allow use of a Toggle component inside a form, as currently it says alpineClickHandler does not exist
return
Forms\Components\Toggle::make($name)
->label(function () use ($name, $label) {
return $label;
})
->onColor(Color::hex('#38c172'))
->offColor(Color::hex('#D63230'))
->offIcon($offIcon)
->onIcon($onIcon)
->inline(false)
->hidden(function () use ($name) {
return $this->config['methods'][$name]['enabled'] ? false : true;
})
->hint(new HtmlString('
<div wire:loading wire:target="data.' . $name . '" style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
text-align: center;
">
<div style="
border: 8px solid rgba(255, 255, 255, 0.3);
border-top: 8px solid white;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
">
</div>
</div>
'))
->afterStateUpdated(function ($state) use ($name) {
$this->submitAction(state: $state, name: $name);
})
->live();
return
Forms\Components\Toggle::make($name)
->label(function () use ($name, $label) {
return $label;
})
->onColor(Color::hex('#38c172'))
->offColor(Color::hex('#D63230'))
->offIcon($offIcon)
->onIcon($onIcon)
->inline(false)
->hidden(function () use ($name) {
return $this->config['methods'][$name]['enabled'] ? false : true;
})
->hint(new HtmlString('
<div wire:loading wire:target="data.' . $name . '" style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
text-align: center;
">
<div style="
border: 8px solid rgba(255, 255, 255, 0.3);
border-top: 8px solid white;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
">
</div>
</div>
'))
->afterStateUpdated(function ($state) use ($name) {
$this->submitAction(state: $state, name: $name);
})
->live();
LeandroFerreira
LeandroFerreira2mo ago
maybe $this->js('register(xx)') in the afterStateUpdated?
Jamie Cee
Jamie Cee5w ago
Thats now leaving me with
ReferenceError: register is not defined
ReferenceError: register is not defined
When I press the toggle, but the function in my js hasn't changed I wasn't able to get anywhere with this, any ideas if im missing something else?
Alpine.data('registerPasskey', () => ({
browserSupportsWebAuthn,
async register(keyName) {
if (!this.browserSupportsWebAuthn()) {
return;
}

const options = await axios.get('/passkeys/register');

let passkey;

try {
passkey = await startRegistration(options.data);

const response = await axios.post('/passkeys', {
passkey: JSON.stringify(passkey),
name: keyName,
});
} catch (e) {
console.log(e);
// this.errors = { name: ['Passkey creation failed. Please try again.'] };

return;
}


},
}));
Alpine.data('registerPasskey', () => ({
browserSupportsWebAuthn,
async register(keyName) {
if (!this.browserSupportsWebAuthn()) {
return;
}

const options = await axios.get('/passkeys/register');

let passkey;

try {
passkey = await startRegistration(options.data);

const response = await axios.post('/passkeys', {
passkey: JSON.stringify(passkey),
name: keyName,
});
} catch (e) {
console.log(e);
// this.errors = { name: ['Passkey creation failed. Please try again.'] };

return;
}


},
}));
Thats my Alpine component, and this is my Toggle
return
Forms\Components\Toggle::make($name)
->label(function () use ($name, $label) {
return $label;
})
->onColor(Color::hex('#38c172'))
->offColor(Color::hex('#D63230'))
->offIcon($offIcon)
->onIcon($onIcon)
->inline(false)
->hidden(function () use ($name) {
return $this->config['methods'][$name]['enabled'] ? false : true;
})
->hint(new HtmlString('
<div wire:loading wire:target="data.' . $name . '" style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
text-align: center;
">
<div style="
border: 8px solid rgba(255, 255, 255, 0.3);
border-top: 8px solid white;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
">
</div>
</div>
'))
->extraAttributes(['x-data' => 'registerPasskey'])
->afterStateUpdated(function ($state) use ($name) {
if ($name == $this->service::SECURITY_KEY_METHOD) {
$this->js('register("' . Str::random(10) . '")');
} else {
$this->submitAction(state: $state, name: $name);
}
})
->live();
return
Forms\Components\Toggle::make($name)
->label(function () use ($name, $label) {
return $label;
})
->onColor(Color::hex('#38c172'))
->offColor(Color::hex('#D63230'))
->offIcon($offIcon)
->onIcon($onIcon)
->inline(false)
->hidden(function () use ($name) {
return $this->config['methods'][$name]['enabled'] ? false : true;
})
->hint(new HtmlString('
<div wire:loading wire:target="data.' . $name . '" style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
text-align: center;
">
<div style="
border: 8px solid rgba(255, 255, 255, 0.3);
border-top: 8px solid white;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
">
</div>
</div>
'))
->extraAttributes(['x-data' => 'registerPasskey'])
->afterStateUpdated(function ($state) use ($name) {
if ($name == $this->service::SECURITY_KEY_METHOD) {
$this->js('register("' . Str::random(10) . '")');
} else {
$this->submitAction(state: $state, name: $name);
}
})
->live();
Want results from more Discord servers?
Add your server